BIT-930: Add UI for Other screen (#477)

This commit is contained in:
Caleb Derosier
2024-01-04 19:24:12 -07:00
committed by GitHub
parent 5eeed09472
commit 9933335ccb
4 changed files with 492 additions and 27 deletions

View File

@@ -1,46 +1,134 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.compose.ui.test.assertIsDisplayed
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.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
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.MutableStateFlow
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class OtherScreenTest : BaseComposeTest() {
@Test
fun `on back click should send BackClick`() {
val viewModel: OtherViewModel = mockk {
every { eventFlow } returns emptyFlow()
every { trySendAction(OtherAction.BackClick) } returns Unit
}
composeTestRule.setContent {
OtherScreen(
viewModel = viewModel,
onNavigateBack = { },
)
}
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(OtherAction.BackClick) }
private var haveCalledNavigateBack = false
private val mutableEventFlow = bufferedMutableSharedFlow<OtherEvent>()
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val viewModel = mockk<OtherViewModel>(relaxed = true) {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
@Test
fun `on NavigateOther should call onNavigateToOther`() {
var haveCalledNavigateBack = false
val viewModel = mockk<OtherViewModel> {
every { eventFlow } returns flowOf(OtherEvent.NavigateBack)
}
@Before
fun setup() {
composeTestRule.setContent {
OtherScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
)
}
}
@Test
fun `on allow screen capture confirm should send AllowScreenCaptureToggle`() {
composeTestRule.onNodeWithText("Allow screen capture").performClick()
composeTestRule.onNodeWithText("Yes").performClick()
composeTestRule.assertNoDialogExists()
verify { viewModel.trySendAction(OtherAction.AllowScreenCaptureToggle(true)) }
}
@Test
fun `on allow screen capture cancel should dismiss dialog`() {
composeTestRule.onNodeWithText("Allow screen capture").performClick()
composeTestRule
.onAllNodesWithText("Cancel")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
composeTestRule.assertNoDialogExists()
}
@Test
fun `on allow screen capture row click should display confirm enable screen capture dialog`() {
composeTestRule.onNodeWithText("Allow screen capture").performClick()
composeTestRule
.onAllNodesWithText("Allow screen capture")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on allow sync toggle should send AllowSyncToggle`() {
composeTestRule.onNodeWithText("Allow sync on refresh").performClick()
verify { viewModel.trySendAction(OtherAction.AllowSyncToggle(true)) }
}
@Test
fun `on back click should send BackClick`() {
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(OtherAction.BackClick) }
}
@Test
fun `on clear clipboard row click should show show clipboard selection dialog`() {
composeTestRule.onNodeWithText("Clear clipboard").performClick()
composeTestRule
.onAllNodesWithText("Clear clipboard")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on clear clipboard dialog item click should send ClearClipboardFrequencyChange`() {
composeTestRule.onNodeWithText("Clear clipboard").performClick()
composeTestRule
.onAllNodesWithText("10 seconds")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
composeTestRule.assertNoDialogExists()
verify {
viewModel.trySendAction(
OtherAction.ClearClipboardFrequencyChange(
clearClipboardFrequency = OtherState.ClearClipboardFrequency.TEN_SECONDS,
),
)
}
}
@Test
fun `on clear clipboard dialog cancel should dismiss dialog`() {
composeTestRule.onNodeWithText("Clear clipboard").performClick()
composeTestRule.onNodeWithText("Cancel").performClick()
composeTestRule.assertNoDialogExists()
}
@Test
fun `on sync now button click should send SyncNowButtonClick`() {
composeTestRule.onNodeWithText("Sync now").performClick()
verify { viewModel.trySendAction(OtherAction.SyncNowButtonClick) }
}
@Test
fun `on NavigateBack should call onNavigateBack`() {
mutableEventFlow.tryEmit(OtherEvent.NavigateBack)
assertTrue(haveCalledNavigateBack)
}
}
private val DEFAULT_STATE = OtherState(
allowScreenCapture = false,
allowSyncOnRefresh = false,
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
lastSyncTime = "5/14/2023 4:52 PM",
)

View File

@@ -1,19 +1,131 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class OtherViewModelTest : BaseViewModelTest() {
val vaultRepository = mockk<VaultRepository>()
@Test
fun `initial state should be correct when not set`() {
val viewModel = createViewModel(state = null)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
}
@Test
fun `initial state should be correct when set`() {
val state = DEFAULT_STATE.copy(
clearClipboardFrequency = OtherState.ClearClipboardFrequency.FIVE_MINUTES,
)
val viewModel = createViewModel(state = state)
assertEquals(state, viewModel.stateFlow.value)
}
@Test
fun `on AllowScreenCaptureToggled should update value in state`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
expectNoEvents()
}
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
viewModel.trySendAction(OtherAction.AllowScreenCaptureToggle(true))
assertEquals(
DEFAULT_STATE.copy(allowScreenCapture = true),
awaitItem(),
)
}
}
@Test
fun `on AllowSyncToggled should update value in state`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
expectNoEvents()
}
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
viewModel.trySendAction(OtherAction.AllowSyncToggle(true))
assertEquals(
DEFAULT_STATE.copy(allowSyncOnRefresh = true),
awaitItem(),
)
}
}
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = OtherViewModel()
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(OtherAction.BackClick)
assertEquals(OtherEvent.NavigateBack, awaitItem())
}
}
@Test
fun `on ClearClipboardFrequencyChange should update state`() = runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
viewModel.trySendAction(
OtherAction.ClearClipboardFrequencyChange(
clearClipboardFrequency = OtherState.ClearClipboardFrequency.ONE_MINUTE,
),
)
assertEquals(
DEFAULT_STATE.copy(
clearClipboardFrequency = OtherState.ClearClipboardFrequency.ONE_MINUTE,
),
awaitItem(),
)
}
}
@Test
fun `on SyncNowButtonClick should sync repo`() = runTest {
every { vaultRepository.sync() } just runs
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(OtherAction.SyncNowButtonClick)
expectNoEvents()
}
verify { vaultRepository.sync() }
}
private fun createViewModel(
state: OtherState? = null,
) = OtherViewModel(
vaultRepo = vaultRepository,
savedStateHandle = SavedStateHandle().apply {
set("state", state)
},
)
companion object {
private val DEFAULT_STATE = OtherState(
allowScreenCapture = false,
allowSyncOnRefresh = false,
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
lastSyncTime = "5/14/2023 4:52 PM",
)
}
}