chore(conductor): Mark track 'Migrate tests to KMP best practices and expand coverage' as complete

This commit is contained in:
James Rich 2026-03-18 16:46:10 -05:00
parent 0e1461b5e4
commit 2395cb91e1
33 changed files with 358 additions and 307 deletions

View file

@ -53,7 +53,8 @@ open class ScannerViewModel(
private val getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase,
private val bleScanner: org.meshtastic.core.ble.BleScanner? = null,
) : ViewModel() {
val showMockInterface: StateFlow<Boolean> = MutableStateFlow(radioInterfaceService.isMockInterface()).asStateFlow()
private val _showMockInterface = MutableStateFlow(false)
val showMockInterface: StateFlow<Boolean> = _showMockInterface.asStateFlow()
private val _errorText = MutableStateFlow<String?>(null)
val errorText: StateFlow<String?> = _errorText.asStateFlow()
@ -65,6 +66,10 @@ open class ScannerViewModel(
private var scanJob: kotlinx.coroutines.Job? = null
init {
_showMockInterface.value = radioInterfaceService.isMockInterface()
}
fun startBleScan() {
if (isBleScanningState.value || bleScanner == null) return

View file

@ -20,9 +20,9 @@ import android.app.Application
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import com.google.android.gms.maps.model.UrlTileProvider
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import dev.mokkery.MockMode
import dev.mokkery.every
import dev.mokkery.mock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@ -54,15 +54,15 @@ import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class MapViewModelTest {
private val application = mockk<Application>(relaxed = true)
private val mapPrefs = mockk<MapPrefs>(relaxed = true)
private val googleMapsPrefs = mockk<GoogleMapsPrefs>(relaxed = true)
private val nodeRepository = mockk<NodeRepository>(relaxed = true)
private val packetRepository = mockk<PacketRepository>(relaxed = true)
private val radioConfigRepository = mockk<RadioConfigRepository>(relaxed = true)
private val radioController = mockk<RadioController>(relaxed = true)
private val customTileProviderRepository = mockk<CustomTileProviderRepository>(relaxed = true)
private val uiPreferencesDataSource = mockk<UiPreferencesDataSource>(relaxed = true)
private val application = mock<Application>(MockMode.autofill)
private val mapPrefs = mock<MapPrefs>(MockMode.autofill)
private val googleMapsPrefs = mock<GoogleMapsPrefs>(MockMode.autofill)
private val nodeRepository = mock<NodeRepository>(MockMode.autofill)
private val packetRepository = mock<PacketRepository>(MockMode.autofill)
private val radioConfigRepository = mock<RadioConfigRepository>(MockMode.autofill)
private val radioController = mock<RadioController>(MockMode.autofill)
private val customTileProviderRepository = mock<CustomTileProviderRepository>(MockMode.autofill)
private val uiPreferencesDataSource = mock<UiPreferencesDataSource>(MockMode.autofill)
private val savedStateHandle = SavedStateHandle(mapOf("waypointId" to null))
private val testDispatcher = StandardTestDispatcher()
@ -89,7 +89,7 @@ class MapViewModelTest {
every { googleMapsPrefs.hiddenLayerUrls } returns MutableStateFlow(emptySet())
every { customTileProviderRepository.getCustomTileProviders() } returns flowOf(emptyList())
every { radioConfigRepository.deviceProfileFlow } returns flowOf(mockk(relaxed = true))
every { radioConfigRepository.deviceProfileFlow } returns flowOf(mock(MockMode.autofill))
every { uiPreferencesDataSource.theme } returns MutableStateFlow(1)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.ourNodeInfo } returns MutableStateFlow(null)
@ -133,13 +133,6 @@ class MapViewModelTest {
@Test
fun `addNetworkMapLayer detects GeoJSON based on extension`() = runTest(testDispatcher) {
mockkStatic(Uri::class)
val mockUri = mockk<Uri>()
every { Uri.parse("https://example.com/data.geojson") } returns mockUri
every { mockUri.scheme } returns "https"
every { mockUri.path } returns "/data.geojson"
every { mockUri.toString() } returns "https://example.com/data.geojson"
viewModel.addNetworkMapLayer("Test Layer", "https://example.com/data.geojson")
advanceUntilIdle()
@ -149,13 +142,6 @@ class MapViewModelTest {
@Test
fun `addNetworkMapLayer defaults to KML for other extensions`() = runTest(testDispatcher) {
mockkStatic(Uri::class)
val mockUri = mockk<Uri>()
every { Uri.parse("https://example.com/map.kml") } returns mockUri
every { mockUri.scheme } returns "https"
every { mockUri.path } returns "/map.kml"
every { mockUri.toString() } returns "https://example.com/map.kml"
viewModel.addNetworkMapLayer("Test KML", "https://example.com/map.kml")
advanceUntilIdle()

View file

@ -43,14 +43,14 @@ import org.meshtastic.core.resources.unmute
import org.meshtastic.core.ui.util.AlertManager
@Single
class NodeManagementActions
open class NodeManagementActions
constructor(
private val nodeRepository: NodeRepository,
private val serviceRepository: ServiceRepository,
private val radioController: RadioController,
private val alertManager: AlertManager,
) {
fun requestRemoveNode(scope: CoroutineScope, node: Node) {
open fun requestRemoveNode(scope: CoroutineScope, node: Node) {
alertManager.showAlert(
titleRes = Res.string.remove,
messageRes = Res.string.remove_node_text,
@ -58,7 +58,7 @@ constructor(
)
}
fun removeNode(scope: CoroutineScope, nodeNum: Int) {
open fun removeNode(scope: CoroutineScope, nodeNum: Int) {
scope.launch(Dispatchers.IO) {
Logger.i { "Removing node '$nodeNum'" }
val packetId = radioController.getPacketId()
@ -67,7 +67,7 @@ constructor(
}
}
fun requestIgnoreNode(scope: CoroutineScope, node: Node) {
open fun requestIgnoreNode(scope: CoroutineScope, node: Node) {
scope.launch {
val message =
getString(if (node.isIgnored) Res.string.ignore_remove else Res.string.ignore_add, node.user.long_name)
@ -79,11 +79,11 @@ constructor(
}
}
fun ignoreNode(scope: CoroutineScope, node: Node) {
open fun ignoreNode(scope: CoroutineScope, node: Node) {
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Ignore(node)) }
}
fun requestMuteNode(scope: CoroutineScope, node: Node) {
open fun requestMuteNode(scope: CoroutineScope, node: Node) {
scope.launch {
val message =
getString(if (node.isMuted) Res.string.mute_remove else Res.string.mute_add, node.user.long_name)
@ -95,11 +95,11 @@ constructor(
}
}
fun muteNode(scope: CoroutineScope, node: Node) {
open fun muteNode(scope: CoroutineScope, node: Node) {
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Mute(node)) }
}
fun requestFavoriteNode(scope: CoroutineScope, node: Node) {
open fun requestFavoriteNode(scope: CoroutineScope, node: Node) {
scope.launch {
val message =
getString(
@ -114,11 +114,11 @@ constructor(
}
}
fun favoriteNode(scope: CoroutineScope, node: Node) {
open fun favoriteNode(scope: CoroutineScope, node: Node) {
scope.launch(Dispatchers.IO) { serviceRepository.onServiceAction(ServiceAction.Favorite(node)) }
}
fun setNodeNotes(scope: CoroutineScope, nodeNum: Int, notes: String) {
open fun setNodeNotes(scope: CoroutineScope, nodeNum: Int, notes: String) {
scope.launch(Dispatchers.IO) {
try {
nodeRepository.setNodeNotes(nodeNum, notes)

View file

@ -27,9 +27,9 @@ import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
import org.meshtastic.proto.Config
@Single
class GetFilteredNodesUseCase constructor(private val nodeRepository: NodeRepository) {
open class GetFilteredNodesUseCase constructor(private val nodeRepository: NodeRepository) {
@Suppress("CyclomaticComplexMethod", "LongMethod")
operator fun invoke(filter: NodeFilterState, sort: NodeSortOption): Flow<List<Node>> = nodeRepository
open operator fun invoke(filter: NodeFilterState, sort: NodeSortOption): Flow<List<Node>> = nodeRepository
.getNodes(
sort = sort,
filter = filter.filterText,

View file

@ -18,46 +18,46 @@ package org.meshtastic.feature.node.list
import kotlinx.coroutines.flow.map
import org.koin.core.annotation.Single
import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.common.UiPreferences
import org.meshtastic.core.model.NodeSortOption
@Single
class NodeFilterPreferences constructor(private val uiPreferencesDataSource: UiPreferencesDataSource) {
val includeUnknown = uiPreferencesDataSource.includeUnknown
val excludeInfrastructure = uiPreferencesDataSource.excludeInfrastructure
val onlyOnline = uiPreferencesDataSource.onlyOnline
val onlyDirect = uiPreferencesDataSource.onlyDirect
val showIgnored = uiPreferencesDataSource.showIgnored
val excludeMqtt = uiPreferencesDataSource.excludeMqtt
open class NodeFilterPreferences constructor(private val uiPreferences: UiPreferences) {
open val includeUnknown = uiPreferences.includeUnknown
open val excludeInfrastructure = uiPreferences.excludeInfrastructure
open val onlyOnline = uiPreferences.onlyOnline
open val onlyDirect = uiPreferences.onlyDirect
open val showIgnored = uiPreferences.showIgnored
open val excludeMqtt = uiPreferences.excludeMqtt
val nodeSortOption =
uiPreferencesDataSource.nodeSort.map { NodeSortOption.entries.getOrElse(it) { NodeSortOption.VIA_FAVORITE } }
open val nodeSortOption =
uiPreferences.nodeSort.map { NodeSortOption.entries.getOrElse(it) { NodeSortOption.VIA_FAVORITE } }
fun setNodeSort(option: NodeSortOption) {
uiPreferencesDataSource.setNodeSort(option.ordinal)
open fun setNodeSort(option: NodeSortOption) {
uiPreferences.setNodeSort(option.ordinal)
}
fun toggleIncludeUnknown() {
uiPreferencesDataSource.setIncludeUnknown(!includeUnknown.value)
open fun toggleIncludeUnknown() {
uiPreferences.setIncludeUnknown(!includeUnknown.value)
}
fun toggleExcludeInfrastructure() {
uiPreferencesDataSource.setExcludeInfrastructure(!excludeInfrastructure.value)
open fun toggleExcludeInfrastructure() {
uiPreferences.setExcludeInfrastructure(!excludeInfrastructure.value)
}
fun toggleOnlyOnline() {
uiPreferencesDataSource.setOnlyOnline(!onlyOnline.value)
open fun toggleOnlyOnline() {
uiPreferences.setOnlyOnline(!onlyOnline.value)
}
fun toggleOnlyDirect() {
uiPreferencesDataSource.setOnlyDirect(!onlyDirect.value)
open fun toggleOnlyDirect() {
uiPreferences.setOnlyDirect(!onlyDirect.value)
}
fun toggleShowIgnored() {
uiPreferencesDataSource.setShowIgnored(!showIgnored.value)
open fun toggleShowIgnored() {
uiPreferences.setShowIgnored(!showIgnored.value)
}
fun toggleExcludeMqtt() {
uiPreferencesDataSource.setExcludeMqtt(!excludeMqtt.value)
open fun toggleExcludeMqtt() {
uiPreferences.setExcludeMqtt(!excludeMqtt.value)
}
}

View file

@ -16,14 +16,18 @@
*/
package org.meshtastic.feature.node.list
import io.kotest.matchers.shouldBe
import androidx.lifecycle.SavedStateHandle
import kotlinx.coroutines.Dispatchers
import app.cash.turbine.test
import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import dev.mokkery.matcher.any
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.NodeSortOption
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.testing.FakeNodeRepository
@ -34,96 +38,85 @@ import org.meshtastic.feature.node.domain.usecase.GetFilteredNodesUseCase
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertNotNull
/**
* Bootstrap tests for NodeListViewModel.
*
* Demonstrates using FakeNodeRepository with a node list feature.
*/
class NodeListViewModelTest {
/*
private lateinit var viewModel: NodeListViewModel
private lateinit var nodeRepository: FakeNodeRepository
private lateinit var radioController: FakeRadioController
private lateinit var radioConfigRepository: RadioConfigRepository
private lateinit var serviceRepository: ServiceRepository
private lateinit var nodeFilterPreferences: NodeFilterPreferences
private lateinit var nodeManagementActions: NodeManagementActions
private lateinit var getFilteredNodesUseCase: GetFilteredNodesUseCase
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
private val nodeFilterPreferences: NodeFilterPreferences = mock(MockMode.autofill)
private val nodeManagementActions: NodeManagementActions = mock(MockMode.autofill)
private val getFilteredNodesUseCase: GetFilteredNodesUseCase = mock(MockMode.autofill)
@BeforeTest
fun setUp() {
kotlinx.coroutines.Dispatchers.setMain(kotlinx.coroutines.Dispatchers.Unconfined)
// Use real fakes
nodeRepository = FakeNodeRepository()
radioController = FakeRadioController()
// Mock remaining dependencies with explicit types
nodeFilterPreferences =
every { nodeSortOption } returns MutableStateFlow(org.meshtastic.core.model.NodeSortOption.LAST_HEARD)
every { includeUnknown } returns MutableStateFlow(true)
every { excludeInfrastructure } returns MutableStateFlow(false)
every { onlyOnline } returns MutableStateFlow(false)
}
@Suppress("UNCHECKED_CAST")
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(org.meshtastic.proto.LocalConfig())
every { radioConfigRepository.deviceProfileFlow } returns MutableStateFlow(org.meshtastic.proto.DeviceProfile())
every { serviceRepository.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
every { nodeFilterPreferences.nodeSortOption } returns MutableStateFlow(NodeSortOption.LAST_HEARD)
every { nodeFilterPreferences.includeUnknown } returns MutableStateFlow(true)
every { nodeFilterPreferences.excludeInfrastructure } returns MutableStateFlow(false)
every { nodeFilterPreferences.onlyOnline } returns MutableStateFlow(false)
every { nodeFilterPreferences.onlyDirect } returns MutableStateFlow(false)
every { nodeFilterPreferences.showIgnored } returns MutableStateFlow(false)
every { nodeFilterPreferences.excludeMqtt } returns MutableStateFlow(false)
viewModel =
NodeListViewModel(
savedStateHandle = SavedStateHandle(),
nodeRepository = nodeRepository,
radioConfigRepository = radioConfigRepository,
serviceRepository = serviceRepository,
radioController = radioController,
nodeManagementActions = nodeManagementActions,
getFilteredNodesUseCase = getFilteredNodesUseCase,
nodeFilterPreferences = nodeFilterPreferences,
)
every { getFilteredNodesUseCase(any(), any()) } returns MutableStateFlow(emptyList())
viewModel = createViewModel()
}
@kotlin.test.AfterTest
fun tearDown() {
kotlinx.coroutines.Dispatchers.resetMain()
private fun createViewModel() = NodeListViewModel(
savedStateHandle = SavedStateHandle(),
nodeRepository = nodeRepository,
radioConfigRepository = radioConfigRepository,
serviceRepository = serviceRepository,
radioController = radioController,
nodeManagementActions = nodeManagementActions,
getFilteredNodesUseCase = getFilteredNodesUseCase,
nodeFilterPreferences = nodeFilterPreferences,
)
@Test
fun testInitialization() {
assertNotNull(viewModel)
}
@Test
fun testInitialization() = runTest {
setUp()
// ViewModel should initialize without errors
assertTrue(true, "NodeListViewModel initialized successfully")
fun `nodeList emits updates when repository changes`() = runTest {
val nodesFlow = MutableStateFlow<List<Node>>(emptyList())
every { getFilteredNodesUseCase(any(), any()) } returns nodesFlow
val vm = createViewModel()
vm.nodeList.test {
// Initial value from stateIn
assertEquals(emptyList(), awaitItem())
// Trigger update
val testNodes = TestDataFactory.createTestNodes(3)
nodesFlow.value = testNodes
assertEquals(3, awaitItem().size)
}
}
@Test
fun testOurNodeInfoFlow() = runTest {
setUp()
// Verify ourNodeInfo StateFlow is accessible
val ourNode = viewModel.ourNodeInfo.value
assertTrue(ourNode == null, "ourNodeInfo starts as null before connection")
fun `connectionState reflects serviceRepository state`() = runTest {
val stateFlow = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
every { serviceRepository.connectionState } returns stateFlow
val vm = createViewModel()
vm.connectionState.test {
assertEquals(ConnectionState.Disconnected, awaitItem())
stateFlow.value = ConnectionState.Connected
assertEquals(ConnectionState.Connected, awaitItem())
}
}
@Test
fun testNodeCounts() = runTest {
setUp()
// Add test nodes to repository
val testNodes = TestDataFactory.createTestNodes(3)
nodeRepository.setNodes(testNodes)
// Verify nodes are in repository
"Test nodes added to repository" shouldBe 3, nodeRepository.nodeDBbyNum.value.size
}
@Test
fun testTotalAndOnlineNodeCounts() = runTest {
setUp()
// Verify count flows are accessible
val totalCount = viewModel.totalNodeCount.value
val onlineCount = viewModel.onlineNodeCount.value
// Both should be accessible without error
assertTrue(true, "Node count flows are accessible")
}
*/
}

View file

@ -16,8 +16,10 @@
*/
package org.meshtastic.feature.node.detail
import io.mockk.mockk
import io.mockk.verify
import dev.mokkery.MockMode
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@ -33,10 +35,10 @@ import org.meshtastic.proto.User
@OptIn(ExperimentalCoroutinesApi::class)
class NodeManagementActionsTest {
private val nodeRepository = mockk<NodeRepository>(relaxed = true)
private val serviceRepository = mockk<ServiceRepository>(relaxed = true)
private val radioController = mockk<RadioController>(relaxed = true)
private val alertManager = mockk<AlertManager>(relaxed = true)
private val nodeRepository = mock<NodeRepository>(MockMode.autofill)
private val serviceRepository = mock<ServiceRepository>(MockMode.autofill)
private val radioController = mock<RadioController>(MockMode.autofill)
private val alertManager = mock<AlertManager>(MockMode.autofill)
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)

View file

@ -16,8 +16,9 @@
*/
package org.meshtastic.feature.node.domain.usecase
import io.mockk.every
import io.mockk.mockk
import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.mock
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
@ -38,7 +39,7 @@ class GetFilteredNodesUseCaseTest {
@Before
fun setUp() {
nodeRepository = mockk()
nodeRepository = mock()
useCase = GetFilteredNodesUseCase(nodeRepository)
}

View file

@ -16,9 +16,10 @@
*/
package org.meshtastic.feature.settings
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import dev.mokkery.MockMode
import dev.mokkery.every
import dev.mokkery.mock
import dev.mokkery.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@ -52,13 +53,13 @@ class LegacySettingsViewModelTest {
private val testDispatcher = StandardTestDispatcher()
private val radioConfigRepository: RadioConfigRepository = mockk(relaxed = true)
private val radioController: RadioController = mockk(relaxed = true)
private val nodeRepository: NodeRepository = mockk(relaxed = true)
private val uiPrefs: UiPrefs = mockk(relaxed = true)
private val buildConfigProvider: BuildConfigProvider = mockk(relaxed = true)
private val databaseManager: DatabaseManager = mockk(relaxed = true)
private val meshLogPrefs: MeshLogPrefs = mockk(relaxed = true)
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
private val radioController: RadioController = mock(MockMode.autofill)
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
private val uiPrefs: UiPrefs = mock(MockMode.autofill)
private val buildConfigProvider: BuildConfigProvider = mock(MockMode.autofill)
private val databaseManager: DatabaseManager = mock(MockMode.autofill)
private val meshLogPrefs: MeshLogPrefs = mock(MockMode.autofill)
private lateinit var setThemeUseCase: SetThemeUseCase
private lateinit var setAppIntroCompletedUseCase: SetAppIntroCompletedUseCase
@ -75,14 +76,14 @@ class LegacySettingsViewModelTest {
fun setUp() {
Dispatchers.setMain(testDispatcher)
setThemeUseCase = mockk(relaxed = true)
setAppIntroCompletedUseCase = mockk(relaxed = true)
setProvideLocationUseCase = mockk(relaxed = true)
setDatabaseCacheLimitUseCase = mockk(relaxed = true)
setMeshLogSettingsUseCase = mockk(relaxed = true)
meshLocationUseCase = mockk(relaxed = true)
exportDataUseCase = mockk(relaxed = true)
isOtaCapableUseCase = mockk(relaxed = true)
setThemeUseCase = mock(MockMode.autofill)
setAppIntroCompletedUseCase = mock(MockMode.autofill)
setProvideLocationUseCase = mock(MockMode.autofill)
setDatabaseCacheLimitUseCase = mock(MockMode.autofill)
setMeshLogSettingsUseCase = mock(MockMode.autofill)
meshLocationUseCase = mock(MockMode.autofill)
exportDataUseCase = mock(MockMode.autofill)
isOtaCapableUseCase = mock(MockMode.autofill)
// Return real StateFlows to avoid ClassCastException
every { databaseManager.cacheLimit } returns MutableStateFlow(100)
@ -95,7 +96,7 @@ class LegacySettingsViewModelTest {
viewModel =
SettingsViewModel(
app = mockk(),
app = mock(),
radioConfigRepository = radioConfigRepository,
radioController = radioController,
nodeRepository = nodeRepository,

View file

@ -16,9 +16,11 @@
*/
package org.meshtastic.feature.settings.filter
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import dev.mokkery.MockMode
import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@ -27,8 +29,8 @@ import org.meshtastic.core.repository.MessageFilter
class FilterSettingsViewModelTest {
private val filterPrefs: FilterPrefs = mockk(relaxed = true)
private val messageFilter: MessageFilter = mockk(relaxed = true)
private val filterPrefs: FilterPrefs = mock(MockMode.autofill)
private val messageFilter: MessageFilter = mock(MockMode.autofill)
private lateinit var viewModel: FilterSettingsViewModel

View file

@ -16,9 +16,11 @@
*/
package org.meshtastic.feature.settings.radio
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import dev.mokkery.MockMode
import dev.mokkery.everySuspend
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verifySuspend
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@ -45,8 +47,8 @@ class CleanNodeDatabaseViewModelTest {
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
cleanNodeDatabaseUseCase = mockk(relaxed = true)
alertManager = mockk(relaxed = true)
cleanNodeDatabaseUseCase = mock(MockMode.autofill)
alertManager = mock(MockMode.autofill)
viewModel = CleanNodeDatabaseViewModel(cleanNodeDatabaseUseCase, alertManager)
}
@ -58,7 +60,7 @@ class CleanNodeDatabaseViewModelTest {
@Test
fun `getNodesToDelete updates state`() = runTest {
val nodes = listOf(Node(num = 1), Node(num = 2))
coEvery { cleanNodeDatabaseUseCase.getNodesToClean(any(), any(), any()) } returns nodes
everySuspend { cleanNodeDatabaseUseCase.getNodesToClean(any(), any(), any()) } returns nodes
viewModel.getNodesToDelete()
advanceUntilIdle()
@ -69,14 +71,14 @@ class CleanNodeDatabaseViewModelTest {
@Test
fun `cleanNodes calls useCase and clears state`() = runTest {
val nodes = listOf(Node(num = 1))
coEvery { cleanNodeDatabaseUseCase.getNodesToClean(any(), any(), any()) } returns nodes
everySuspend { cleanNodeDatabaseUseCase.getNodesToClean(any(), any(), any()) } returns nodes
viewModel.getNodesToDelete()
advanceUntilIdle()
viewModel.cleanNodes()
advanceUntilIdle()
coVerify { cleanNodeDatabaseUseCase.cleanNodes(listOf(1)) }
verifySuspend { cleanNodeDatabaseUseCase.cleanNodes(listOf(1)) }
assertEquals(0, viewModel.nodesToDelete.value.size)
}
}