mirror of
https://github.com/bitwarden/android.git
synced 2026-05-31 09:46:08 -05:00
BIT-929: Add UI for Appearance screen (#478)
This commit is contained in:
@@ -1,46 +1,110 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
|
||||
|
||||
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 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 AppearanceScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
val viewModel: AppearanceViewModel = mockk {
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(AppearanceAction.BackClick) } returns Unit
|
||||
}
|
||||
private var haveCalledNavigateBack = false
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<AppearanceEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<AppearanceViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
AppearanceScreen(
|
||||
onNavigateBack = { haveCalledNavigateBack = true },
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(AppearanceAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateAbout should call onNavigateToAbout`() {
|
||||
var haveCalledNavigateBack = false
|
||||
val viewModel = mockk<AppearanceViewModel> {
|
||||
every { eventFlow } returns flowOf(AppearanceEvent.NavigateBack)
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
AppearanceScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { haveCalledNavigateBack = true },
|
||||
fun `on language row click should display language selection dialog`() {
|
||||
composeTestRule.onNodeWithText("Language").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Language")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on language selection dialog item click should send LanguageChange`() {
|
||||
composeTestRule.onNodeWithText("Language").performClick()
|
||||
composeTestRule.onNodeWithText("English").performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
AppearanceAction.LanguageChange(
|
||||
newLanguage = AppearanceState.Language.ENGLISH,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on theme row click should display theme selection dialog`() {
|
||||
composeTestRule.onNodeWithText("Theme").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Theme")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on theme selection dialog item click should send ThemeChange`() {
|
||||
composeTestRule.onNodeWithText("Theme").performClick()
|
||||
composeTestRule.onNodeWithText("Dark").performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
AppearanceAction.ThemeChange(
|
||||
newTheme = AppearanceState.Theme.DARK,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on show website icons row click should send ShowWebsiteIconsToggled`() {
|
||||
composeTestRule.onNodeWithText("Show website icons").performClick()
|
||||
verify { viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsToggle(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(AppearanceEvent.NavigateBack)
|
||||
assertTrue(haveCalledNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = AppearanceState(
|
||||
language = AppearanceState.Language.DEFAULT,
|
||||
showWebsiteIcons = false,
|
||||
theme = AppearanceState.Theme.DEFAULT,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -7,13 +8,91 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AppearanceViewModelTest : BaseViewModelTest() {
|
||||
@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(theme = AppearanceState.Theme.DARK)
|
||||
val viewModel = createViewModel(state = state)
|
||||
assertEquals(state, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = AppearanceViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AppearanceAction.BackClick)
|
||||
assertEquals(AppearanceEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on LanguageChange should update state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
AppearanceAction.LanguageChange(AppearanceState.Language.ENGLISH),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(language = AppearanceState.Language.ENGLISH),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ShowWebsiteIconsToggle should update value in state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(AppearanceAction.ShowWebsiteIconsToggle(true))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(showWebsiteIcons = true),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ThemeChange should update state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(AppearanceAction.ThemeChange(AppearanceState.Theme.DARK))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(theme = AppearanceState.Theme.DARK),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: AppearanceState? = null,
|
||||
) = AppearanceViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
},
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_STATE = AppearanceState(
|
||||
language = AppearanceState.Language.DEFAULT,
|
||||
showWebsiteIcons = false,
|
||||
theme = AppearanceState.Theme.DEFAULT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user