From a254cb9e6fcf85882317d6a4385e69f86cb95bcc Mon Sep 17 00:00:00 2001 From: Petr Date: Thu, 26 Feb 2026 08:00:03 +0100 Subject: [PATCH] Add some tests --- .../app/hypostats/ui/MainViewModelTest.kt | 52 +++++++++++++++++++ .../app/hypostats/ui/log/LogViewModelTest.kt | 37 +++++++++++++ .../hypostats/util/FakeTreatmentRepository.kt | 6 ++- .../app/hypostats/util/MainDispatcherRule.kt | 23 ++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 app/src/test/kotlin/app/hypostats/ui/MainViewModelTest.kt create mode 100644 app/src/test/kotlin/app/hypostats/ui/log/LogViewModelTest.kt create mode 100644 app/src/test/kotlin/app/hypostats/util/MainDispatcherRule.kt diff --git a/app/src/test/kotlin/app/hypostats/ui/MainViewModelTest.kt b/app/src/test/kotlin/app/hypostats/ui/MainViewModelTest.kt new file mode 100644 index 0000000..c4f5d4f --- /dev/null +++ b/app/src/test/kotlin/app/hypostats/ui/MainViewModelTest.kt @@ -0,0 +1,52 @@ +package app.hypostats.ui + +import app.hypostats.ui.model.AppTab +import app.hypostats.ui.model.DrawerDestination +import app.hypostats.util.FakeSettingsRepository +import app.hypostats.util.MainDispatcherRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class MainViewModelTest { + @get:Rule + val mainDispatcherRule = MainDispatcherRule(UnconfinedTestDispatcher()) + + private fun TestScope.collectState(viewModel: MainViewModel) { + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + viewModel.state.collect {} + } + } + + @Test + fun `selectTab updates selected tab`() = + runTest { + val viewModel = MainViewModel(FakeSettingsRepository()) + collectState(viewModel) + + assertEquals(AppTab.HYPO, viewModel.state.value.selectedTab) + + viewModel.selectTab(AppTab.STATS) + + assertEquals(AppTab.STATS, viewModel.state.value.selectedTab) + } + + @Test + fun `selectDrawerDestination updates selected drawer destination`() = + runTest { + val viewModel = MainViewModel(FakeSettingsRepository()) + collectState(viewModel) + + assertEquals(DrawerDestination.HOME, viewModel.state.value.selectedDrawerDestination) + + viewModel.selectDrawerDestination(DrawerDestination.ABOUT) + + assertEquals(DrawerDestination.ABOUT, viewModel.state.value.selectedDrawerDestination) + } +} diff --git a/app/src/test/kotlin/app/hypostats/ui/log/LogViewModelTest.kt b/app/src/test/kotlin/app/hypostats/ui/log/LogViewModelTest.kt new file mode 100644 index 0000000..3bcff3b --- /dev/null +++ b/app/src/test/kotlin/app/hypostats/ui/log/LogViewModelTest.kt @@ -0,0 +1,37 @@ +package app.hypostats.ui.log + +import app.hypostats.domain.model.Treatment +import app.hypostats.util.FakeTreatmentRepository +import app.hypostats.util.MainDispatcherRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import java.time.Instant + +@OptIn(ExperimentalCoroutinesApi::class) +class LogViewModelTest { + @get:Rule + val mainDispatcherRule = MainDispatcherRule(UnconfinedTestDispatcher()) + + private fun TestScope.collectTreatments(viewModel: LogViewModel) { + backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + viewModel.treatments.collect {} + } + } + + @Test + fun `treatments are sorted newest first`() = + runTest { + val older = Treatment(Instant.parse("2024-01-01T00:00:00Z"), 10) + val newer = Treatment(Instant.parse("2024-01-02T00:00:00Z"), 10) + val viewModel = LogViewModel(FakeTreatmentRepository(listOf(older, newer))) + collectTreatments(viewModel) + + assertEquals(listOf(newer, older), viewModel.treatments.value) + } +} diff --git a/app/src/test/kotlin/app/hypostats/util/FakeTreatmentRepository.kt b/app/src/test/kotlin/app/hypostats/util/FakeTreatmentRepository.kt index a987646..10a1fc8 100644 --- a/app/src/test/kotlin/app/hypostats/util/FakeTreatmentRepository.kt +++ b/app/src/test/kotlin/app/hypostats/util/FakeTreatmentRepository.kt @@ -7,8 +7,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import java.time.Instant -class FakeTreatmentRepository : TreatmentRepository { - private val treatmentsFlow = MutableStateFlow>(emptyList()) +class FakeTreatmentRepository( + initialTreatments: List = emptyList(), +) : TreatmentRepository { + private val treatmentsFlow = MutableStateFlow>(initialTreatments) private val trackingStartDateFlow = MutableStateFlow(Instant.EPOCH) override fun getAllTreatments(): Flow> = treatmentsFlow.asStateFlow() diff --git a/app/src/test/kotlin/app/hypostats/util/MainDispatcherRule.kt b/app/src/test/kotlin/app/hypostats/util/MainDispatcherRule.kt new file mode 100644 index 0000000..2f68b67 --- /dev/null +++ b/app/src/test/kotlin/app/hypostats/util/MainDispatcherRule.kt @@ -0,0 +1,23 @@ +@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) + +package app.hypostats.util + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +class MainDispatcherRule( + val testDispatcher: TestDispatcher = StandardTestDispatcher(), +) : TestWatcher() { + override fun starting(description: Description) { + Dispatchers.setMain(testDispatcher) + } + + override fun finished(description: Description) { + Dispatchers.resetMain() + } +}