mirror of
https://github.com/bitwarden/android.git
synced 2026-05-30 16:43:22 -05:00
BIT-457: Add Vault Settings and Folders screen UI (#457)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.folders
|
||||
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class FoldersScreenTest : BaseComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
private val mutableEventFlow = MutableSharedFlow<FoldersEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(Unit)
|
||||
val viewModel = mockk<FoldersViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
FoldersScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close button click should send CloseButtonClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Close").performClick()
|
||||
verify { viewModel.trySendAction(FoldersAction.CloseButtonClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `add folder button click should send AddFolderButtonClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Add item").performClick()
|
||||
verify {
|
||||
viewModel.trySendAction(FoldersAction.AddFolderButtonClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(FoldersEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.folders
|
||||
|
||||
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 FoldersViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FoldersAction.CloseButtonClick)
|
||||
assertEquals(FoldersEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddFolderButtonClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FoldersAction.AddFolderButtonClick)
|
||||
assertEquals(
|
||||
FoldersEvent.ShowToast("Not yet implemented."),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): FoldersViewModel = FoldersViewModel()
|
||||
}
|
||||
@@ -1,46 +1,98 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
|
||||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class VaultSettingsScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
val viewModel: VaultSettingsViewModel = mockk {
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(VaultSettingsAction.BackClick) } returns Unit
|
||||
}
|
||||
private var onNavigateBackCalled = false
|
||||
private var onNavigateToFoldersCalled = false
|
||||
private val mutableEventFlow = MutableSharedFlow<VaultSettingsEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(Unit)
|
||||
val viewModel = mockk<VaultSettingsViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
VaultSettingsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { },
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToFolders = { onNavigateToFoldersCalled = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
every { viewModel.trySendAction(VaultSettingsAction.BackClick) } returns Unit
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(VaultSettingsAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateAbout should call onNavigateToVault`() {
|
||||
var haveCalledNavigateBack = false
|
||||
val viewModel = mockk<VaultSettingsViewModel> {
|
||||
every { eventFlow } returns flowOf(VaultSettingsEvent.NavigateBack)
|
||||
fun `export vault click should send ExportVaultClick`() {
|
||||
composeTestRule.onNodeWithText("Export vault").performClick()
|
||||
verify {
|
||||
viewModel.trySendAction(VaultSettingsAction.ExportVaultClick)
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
VaultSettingsScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { haveCalledNavigateBack = true },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `import items click should display dialog and confirming should send ImportItemsClick`() {
|
||||
composeTestRule.onNodeWithText("Import items").performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText("Continue")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
|
||||
}
|
||||
assertTrue(haveCalledNavigateBack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `import items click should display dialog & canceling should not send ImportItemsClick`() {
|
||||
composeTestRule.onNodeWithText("Import items").performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText("Cancel")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 0) {
|
||||
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToFolders should call onNavigateToFolders`() {
|
||||
mutableEventFlow.tryEmit(VaultSettingsEvent.NavigateToFolders)
|
||||
assertTrue(onNavigateToFoldersCalled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,37 @@ import org.junit.jupiter.api.Test
|
||||
class VaultSettingsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = VaultSettingsViewModel()
|
||||
fun `BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultSettingsAction.BackClick)
|
||||
assertEquals(VaultSettingsEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ExportVaultClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultSettingsAction.ExportVaultClick)
|
||||
assertEquals(
|
||||
VaultSettingsEvent.ShowToast("Not yet implemented."),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ImportItemsClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultSettingsAction.ImportItemsClick)
|
||||
assertEquals(
|
||||
VaultSettingsEvent.ShowToast("Not yet implemented."),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(): VaultSettingsViewModel = VaultSettingsViewModel()
|
||||
}
|
||||
|
||||
@@ -9,10 +9,20 @@ import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
private val fakeNavHostController = FakeNavHostController()
|
||||
private val mutableEventFlow = MutableSharedFlow<VaultUnlockedNavBarEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(Unit)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
private val expectedNavOptions = navOptions {
|
||||
// When changing root navigation state, pop everything else off the back stack:
|
||||
@@ -24,9 +34,8 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
restoreState = true
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vault tab click should send VaultTabClick action`() {
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true)
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
@@ -37,90 +46,42 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToFolders = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("My vault").performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vault tab click should send VaultTabClick action`() {
|
||||
composeTestRule.onNodeWithText("My vault").performClick()
|
||||
verify { viewModel.trySendAction(VaultUnlockedNavBarAction.VaultTabClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToVaultScreen should navigate to VaultScreen`() {
|
||||
val vaultUnlockedNavBarEventFlow = MutableSharedFlow<VaultUnlockedNavBarEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns vaultUnlockedNavBarEventFlow
|
||||
}
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") }
|
||||
vaultUnlockedNavBarEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToVaultScreen)
|
||||
runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_graph",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
composeTestRule.runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") }
|
||||
mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToVaultScreen)
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_graph",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `send tab click should send SendTabClick action`() {
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true)
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Send").performClick()
|
||||
}
|
||||
composeTestRule.onNodeWithText("Send").performClick()
|
||||
verify { viewModel.trySendAction(VaultUnlockedNavBarAction.SendTabClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToSendScreen should navigate to SendScreen`() {
|
||||
val vaultUnlockedNavBarEventFlow = MutableSharedFlow<VaultUnlockedNavBarEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns vaultUnlockedNavBarEventFlow
|
||||
}
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") }
|
||||
vaultUnlockedNavBarEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen)
|
||||
mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen)
|
||||
runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "send",
|
||||
@@ -132,48 +93,15 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `generator tab click should send GeneratorTabClick action`() {
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true)
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Generator").performClick()
|
||||
}
|
||||
composeTestRule.onNodeWithText("Generator").performClick()
|
||||
verify { viewModel.trySendAction(VaultUnlockedNavBarAction.GeneratorTabClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToGeneratorScreen should navigate to GeneratorScreen`() {
|
||||
val vaultUnlockedNavBarEventFlow = MutableSharedFlow<VaultUnlockedNavBarEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns vaultUnlockedNavBarEventFlow
|
||||
}
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") }
|
||||
vaultUnlockedNavBarEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen)
|
||||
mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen)
|
||||
runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "generator",
|
||||
@@ -185,48 +113,15 @@ class VaultUnlockedNavBarScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `settings tab click should send SendTabClick action`() {
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true)
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
onNodeWithText("Settings").performClick()
|
||||
}
|
||||
composeTestRule.onNodeWithText("Settings").performClick()
|
||||
verify { viewModel.trySendAction(VaultUnlockedNavBarAction.SettingsTabClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NavigateToSettingsScreen should navigate to SettingsScreen`() {
|
||||
val vaultUnlockedNavBarEventFlow = MutableSharedFlow<VaultUnlockedNavBarEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
val viewModel = mockk<VaultUnlockedNavBarViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns vaultUnlockedNavBarEventFlow
|
||||
}
|
||||
composeTestRule.apply {
|
||||
setContent {
|
||||
VaultUnlockedNavBarScreen(
|
||||
viewModel = viewModel,
|
||||
navController = fakeNavHostController,
|
||||
onNavigateToVaultAddItem = {},
|
||||
onNavigateToVaultItem = {},
|
||||
onNavigateToVaultEditItem = {},
|
||||
onNavigateToNewSend = {},
|
||||
onNavigateToDeleteAccount = {},
|
||||
onNavigateToPasswordHistory = {},
|
||||
)
|
||||
}
|
||||
runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") }
|
||||
vaultUnlockedNavBarEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSettingsScreen)
|
||||
mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSettingsScreen)
|
||||
runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "settings_graph",
|
||||
|
||||
Reference in New Issue
Block a user