mirror of
https://github.com/bitwarden/android.git
synced 2026-03-21 05:40:45 -05:00
PM-19978: Build out flight recorder UI (#5009)
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
/**
|
||||
* The selectable durations allowed for the flight recorder.
|
||||
*/
|
||||
enum class FlightRecorderDuration(
|
||||
val milliseconds: Long,
|
||||
) {
|
||||
ONE_HOUR(milliseconds = 3_600_000L),
|
||||
EIGHT_HOURS(milliseconds = 28_800_000L),
|
||||
TWENTY_FOUR_HOURS(milliseconds = 86_400_000L),
|
||||
ONE_WEEK(milliseconds = 604_800_000L),
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.toAnnotatedString
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.util.spanStyleOf
|
||||
|
||||
/**
|
||||
* Uses an annotated string resource to create a string with clickable text.
|
||||
@@ -30,7 +31,10 @@ fun BitwardenHyperTextLink(
|
||||
color: Color = BitwardenTheme.colorScheme.text.secondary,
|
||||
) {
|
||||
Text(
|
||||
text = annotatedResId.toAnnotatedString(args = args) { key ->
|
||||
text = annotatedResId.toAnnotatedString(
|
||||
args = args,
|
||||
style = spanStyleOf(color = color, textStyle = style),
|
||||
) { key ->
|
||||
when (key) {
|
||||
annotationKey -> onClick()
|
||||
}
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
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.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.FlightRecorderDuration
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenHyperTextLink
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.util.displayText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Displays the flight recorder configuration screen.
|
||||
@@ -23,10 +49,15 @@ import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
fun FlightRecorderScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
viewModel: FlightRecorderViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
EventsEffect(viewModel) { event ->
|
||||
when (event) {
|
||||
FlightRecorderEvent.NavigateBack -> onNavigateBack()
|
||||
FlightRecorderEvent.NavigateToHelpCenter -> {
|
||||
intentManager.launchUri(uri = "https://bitwarden.com/help".toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
@@ -40,10 +71,130 @@ fun FlightRecorderScreen(
|
||||
{ viewModel.trySendAction(FlightRecorderAction.BackClick) }
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.save),
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(FlightRecorderAction.SaveClick) }
|
||||
},
|
||||
modifier = Modifier.testTag("SaveButton"),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) {
|
||||
// TODO: PM-19592 Create the flight recorder UI.
|
||||
FlightRecorderContent(
|
||||
state = state,
|
||||
onDurationSelected = remember(viewModel) {
|
||||
{ viewModel.trySendAction(FlightRecorderAction.DurationSelect(it)) }
|
||||
},
|
||||
onHelpCenterClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(FlightRecorderAction.HelpCenterClick) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun FlightRecorderContent(
|
||||
state: FlightRecorderState,
|
||||
onDurationSelected: (FlightRecorderDuration) -> Unit,
|
||||
onHelpCenterClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.verticalScroll(state = rememberScrollState()),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.experiencing_an_issue),
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
style = BitwardenTheme.typography.titleMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.enable_temporary_logging_to_collect_and_inspect_logs_locally,
|
||||
),
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.to_get_started_set_a_logging_duration),
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
style = BitwardenTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
DurationSelectButton(
|
||||
selectedOption = state.selectedDuration,
|
||||
onOptionSelected = onDurationSelected,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 24.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.logs_will_be_automatically_deleted_after_30_days),
|
||||
color = BitwardenTheme.colorScheme.text.secondary,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenHyperTextLink(
|
||||
annotatedResId = R.string.for_details_on_what_is_and_isnt_logged,
|
||||
annotationKey = "helpCenter",
|
||||
accessibilityString = stringResource(id = R.string.bitwarden_help_center),
|
||||
onClick = onHelpCenterClick,
|
||||
color = BitwardenTheme.colorScheme.text.secondary,
|
||||
style = BitwardenTheme.typography.bodySmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DurationSelectButton(
|
||||
selectedOption: FlightRecorderDuration,
|
||||
onOptionSelected: (FlightRecorderDuration) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
val options = FlightRecorderDuration.entries.map { it.displayText() }.toImmutableList()
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.logging_duration),
|
||||
options = options,
|
||||
selectedOption = selectedOption.displayText(),
|
||||
onOptionSelected = { selectedOption ->
|
||||
onOptionSelected(
|
||||
FlightRecorderDuration
|
||||
.entries
|
||||
.first { selectedOption == it.displayText.toString(resources) },
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.FlightRecorderDuration
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
@@ -15,23 +17,43 @@ class FlightRecorderViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<FlightRecorderState, FlightRecorderEvent, FlightRecorderAction>(
|
||||
// We load the state from the savedStateHandle for testing purposes.
|
||||
initialState = savedStateHandle[KEY_STATE] ?: FlightRecorderState,
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: FlightRecorderState(
|
||||
selectedDuration = FlightRecorderDuration.ONE_HOUR,
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: FlightRecorderAction) {
|
||||
when (action) {
|
||||
FlightRecorderAction.BackClick -> handleBackClick()
|
||||
is FlightRecorderAction.DurationSelect -> handleOnDurationSelect(action)
|
||||
FlightRecorderAction.HelpCenterClick -> handleHelpCenterClick()
|
||||
FlightRecorderAction.SaveClick -> handleSaveClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBackClick() {
|
||||
sendEvent(FlightRecorderEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleOnDurationSelect(action: FlightRecorderAction.DurationSelect) {
|
||||
mutableStateFlow.update { it.copy(selectedDuration = action.duration) }
|
||||
}
|
||||
|
||||
private fun handleHelpCenterClick() {
|
||||
sendEvent(FlightRecorderEvent.NavigateToHelpCenter)
|
||||
}
|
||||
|
||||
private fun handleSaveClick() {
|
||||
// TODO: PM-19592 Persist the flight recorder state.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the UI state for the flight recorder screen.
|
||||
*/
|
||||
data object FlightRecorderState
|
||||
data class FlightRecorderState(
|
||||
val selectedDuration: FlightRecorderDuration,
|
||||
)
|
||||
|
||||
/**
|
||||
* Models events for the flight recorder screen.
|
||||
@@ -41,6 +63,11 @@ sealed class FlightRecorderEvent {
|
||||
* Navigates back.
|
||||
*/
|
||||
data object NavigateBack : FlightRecorderEvent()
|
||||
|
||||
/**
|
||||
* Launches the the help center link.
|
||||
*/
|
||||
data object NavigateToHelpCenter : FlightRecorderEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,4 +78,21 @@ sealed class FlightRecorderAction {
|
||||
* Indicates that the user clicked the close button.
|
||||
*/
|
||||
data object BackClick : FlightRecorderAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user clicked the help center link.
|
||||
*/
|
||||
data object HelpCenterClick : FlightRecorderAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user clicked the save button.
|
||||
*/
|
||||
data object SaveClick : FlightRecorderAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user selected a duration.
|
||||
*/
|
||||
data class DurationSelect(
|
||||
val duration: FlightRecorderDuration,
|
||||
) : FlightRecorderAction()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder.util
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.FlightRecorderDuration
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* A helper function to map the [FlightRecorderDuration] to a displayable label.
|
||||
*/
|
||||
val FlightRecorderDuration.displayText: Text
|
||||
get() = when (this) {
|
||||
FlightRecorderDuration.ONE_HOUR -> R.string.flight_recorder_one_hour.asText()
|
||||
FlightRecorderDuration.EIGHT_HOURS -> R.string.flight_recorder_eight_hours.asText()
|
||||
FlightRecorderDuration.TWENTY_FOUR_HOURS -> {
|
||||
R.string.flight_recorder_twenty_four_hours.asText()
|
||||
}
|
||||
|
||||
FlightRecorderDuration.ONE_WEEK -> R.string.flight_recorder_one_week.asText()
|
||||
}
|
||||
@@ -1226,4 +1226,14 @@ Do you want to switch to this account?</string>
|
||||
<string name="enable_flight_recorder_title">Enable flight recorder</string>
|
||||
<string name="recorded_logs_title">Recorded logs</string>
|
||||
<string name="no_logs_recorded">No logs recorded</string>
|
||||
<string name="experiencing_an_issue">Experiencing an issue?</string>
|
||||
<string name="enable_temporary_logging_to_collect_and_inspect_logs_locally">Enable temporary logging to collect and inspect logs locally. When enabled, application states and network calls may be logged—never sensitive vault information.</string>
|
||||
<string name="to_get_started_set_a_logging_duration">To get started, set a logging duration. Logging
will automatically turn off after this period, giving you time to capture activity while reproducing
any issues.</string>
|
||||
<string name="logs_will_be_automatically_deleted_after_30_days">Logs will be automatically deleted after 30 days. Bitwarden is only able to access your log data when you share it.</string>
|
||||
<string name="for_details_on_what_is_and_isnt_logged">For details on what is and isn’t logged, visit the\n<annotation link="helpCenter">Bitwarden help center.</annotation></string>
|
||||
<string name="logging_duration">Logging duration</string>
|
||||
<string name="flight_recorder_one_hour">1 hour</string>
|
||||
<string name="flight_recorder_eight_hours">8 hours</string>
|
||||
<string name="flight_recorder_twenty_four_hours">24 hours</string>
|
||||
<string name="flight_recorder_one_week">1 week</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.isDisplayed
|
||||
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 androidx.core.net.toUri
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.FlightRecorderDuration
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.x8bit.bitwarden.ui.util.performCustomAccessibilityAction
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
@@ -22,9 +35,15 @@ class FlightRecorderScreenTest : BaseComposeTest() {
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
private val intentManager = mockk<IntentManager> {
|
||||
every { launchUri(uri = any()) } just runs
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
setContent {
|
||||
setContent(
|
||||
intentManager = intentManager,
|
||||
) {
|
||||
FlightRecorderScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
viewModel = viewModel,
|
||||
@@ -42,11 +61,72 @@ class FlightRecorderScreenTest : BaseComposeTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on save click should emit SaveClick action`() {
|
||||
composeTestRule.onNodeWithText(text = "Save").performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(FlightRecorderAction.SaveClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on help center click should emit HelpCenterClick action`() {
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Bitwarden help center", substring = true)
|
||||
.performScrollTo()
|
||||
.performCustomAccessibilityAction(label = "Bitwarden help center")
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(FlightRecorderAction.HelpCenterClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on logging duration click should display select dialog`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "1 hour. Logging duration")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Logging duration")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.isDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on selection of new duration should emit action`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "1 hour. Logging duration")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "24 hours")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(
|
||||
FlightRecorderAction.DurationSelect(FlightRecorderDuration.TWENTY_FOUR_HOURS),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack event should invoke onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(FlightRecorderEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToHelpCenter event should launch intent for help center`() {
|
||||
mutableEventFlow.tryEmit(FlightRecorderEvent.NavigateToHelpCenter)
|
||||
verify(exactly = 1) {
|
||||
intentManager.launchUri(uri = "https://bitwarden.com/help".toUri())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: FlightRecorderState = FlightRecorderState
|
||||
private val DEFAULT_STATE: FlightRecorderState =
|
||||
FlightRecorderState(
|
||||
selectedDuration = FlightRecorderDuration.ONE_HOUR,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.flightrecorder
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.FlightRecorderDuration
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -24,6 +25,47 @@ class FlightRecorderViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SaveClick action should do nothing`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FlightRecorderAction.SaveClick)
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on HelpCenterClick action should send the NavigateToHelpCenter event`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(FlightRecorderAction.HelpCenterClick)
|
||||
assertEquals(FlightRecorderEvent.NavigateToHelpCenter, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on DurationSelect action should update the selectedDuration state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
viewModel.trySendAction(
|
||||
FlightRecorderAction.DurationSelect(duration = FlightRecorderDuration.ONE_WEEK),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(selectedDuration = FlightRecorderDuration.ONE_WEEK),
|
||||
awaitItem(),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(
|
||||
FlightRecorderAction.DurationSelect(duration = FlightRecorderDuration.EIGHT_HOURS),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(selectedDuration = FlightRecorderDuration.EIGHT_HOURS),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: FlightRecorderState? = null,
|
||||
): FlightRecorderViewModel =
|
||||
@@ -34,4 +76,7 @@ class FlightRecorderViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: FlightRecorderState = FlightRecorderState
|
||||
private val DEFAULT_STATE: FlightRecorderState =
|
||||
FlightRecorderState(
|
||||
selectedDuration = FlightRecorderDuration.ONE_HOUR,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user