refactor: coroutine dispatchers and modernize testing infrastructure (#4901)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-23 20:31:48 -05:00 committed by GitHub
parent 664ebf218e
commit 96060a0a4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 621 additions and 182 deletions

View file

@ -42,6 +42,7 @@ class AndroidScannerViewModel(
radioInterfaceService: RadioInterfaceService,
recentAddressesDataSource: RecentAddressesDataSource,
getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase,
dispatchers: org.meshtastic.core.di.CoroutineDispatchers,
private val bluetoothRepository: BluetoothRepository,
private val usbRepository: UsbRepository,
bleScanner: org.meshtastic.core.ble.BleScanner? = null,
@ -51,6 +52,7 @@ class AndroidScannerViewModel(
radioInterfaceService,
recentAddressesDataSource,
getDiscoveredDevicesUseCase,
dispatchers,
bleScanner,
) {
override fun requestBonding(entry: DeviceListEntry.Ble) {

View file

@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.koin.core.annotation.KoinViewModel
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.datastore.RecentAddressesDataSource
import org.meshtastic.core.datastore.model.RecentAddress
import org.meshtastic.core.model.RadioController
@ -52,6 +51,7 @@ open class ScannerViewModel(
private val radioInterfaceService: RadioInterfaceService,
private val recentAddressesDataSource: RecentAddressesDataSource,
private val getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase,
private val dispatchers: org.meshtastic.core.di.CoroutineDispatchers,
private val bleScanner: org.meshtastic.core.ble.BleScanner? = null,
) : ViewModel() {
private val _showMockInterface = MutableStateFlow(false)
@ -84,7 +84,7 @@ open class ScannerViewModel(
timeout = kotlin.time.Duration.INFINITE,
serviceUuid = org.meshtastic.core.ble.MeshtasticBleConstants.SERVICE_UUID,
)
.flowOn(ioDispatcher)
.flowOn(dispatchers.io)
.collect { device ->
if (!scannedBleDevices.value.containsKey(device.address)) {
scannedBleDevices.update { current -> current + (device.address to device) }
@ -123,7 +123,7 @@ open class ScannerViewModel(
// Sort by name
(bonded + unbondedScanned).sortedBy { it.name }
}
.flowOn(kotlinx.coroutines.Dispatchers.Default)
.flowOn(dispatchers.default)
.distinctUntilChanged()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

View file

@ -23,11 +23,12 @@ import dev.mokkery.every
import dev.mokkery.matcher.any
import dev.mokkery.mock
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.meshtastic.core.datastore.RecentAddressesDataSource
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.RadioInterfaceService
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.core.testing.FakeServiceRepository
import org.meshtastic.feature.connections.model.DiscoveredDevices
import org.meshtastic.feature.connections.model.GetDiscoveredDevicesUseCase
import kotlin.test.BeforeTest
@ -35,17 +36,17 @@ import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ScannerViewModelTest {
private lateinit var viewModel: ScannerViewModel
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
private val radioController: RadioController = mock(MockMode.autofill)
private val serviceRepository = FakeServiceRepository()
private val radioController = FakeRadioController()
private val radioInterfaceService: RadioInterfaceService = mock(MockMode.autofill)
private val recentAddressesDataSource: RecentAddressesDataSource = mock(MockMode.autofill)
private val getDiscoveredDevicesUseCase: GetDiscoveredDevicesUseCase = mock(MockMode.autofill)
private val bleScanner: org.meshtastic.core.ble.BleScanner = mock(MockMode.autofill)
private val connectionProgressFlow = MutableStateFlow<String?>(null)
private val discoveredDevicesFlow = MutableStateFlow(DiscoveredDevices())
@BeforeTest
@ -54,11 +55,10 @@ class ScannerViewModelTest {
every { radioInterfaceService.currentDeviceAddressFlow } returns MutableStateFlow(null)
every { radioInterfaceService.supportedDeviceTypes } returns emptyList()
every { serviceRepository.connectionProgress } returns connectionProgressFlow
every { getDiscoveredDevicesUseCase.invoke(any()) } returns discoveredDevicesFlow
every { recentAddressesDataSource.recentAddresses } returns MutableStateFlow(emptyList())
connectionProgressFlow.value = null
serviceRepository.setConnectionProgress("")
discoveredDevicesFlow.value = DiscoveredDevices()
viewModel =
@ -68,6 +68,12 @@ class ScannerViewModelTest {
radioInterfaceService = radioInterfaceService,
recentAddressesDataSource = recentAddressesDataSource,
getDiscoveredDevicesUseCase = getDiscoveredDevicesUseCase,
dispatchers =
org.meshtastic.core.di.CoroutineDispatchers(
io = UnconfinedTestDispatcher(),
main = UnconfinedTestDispatcher(),
default = UnconfinedTestDispatcher(),
),
bleScanner = bleScanner,
)
}
@ -80,8 +86,8 @@ class ScannerViewModelTest {
@Test
fun `errorText reflects connectionProgress`() = runTest {
viewModel.errorText.test {
assertEquals(null, awaitItem())
connectionProgressFlow.value = "Connecting..."
assertEquals("", awaitItem())
serviceRepository.setConnectionProgress("Connecting...")
assertEquals("Connecting...", awaitItem())
cancelAndIgnoreRemainingEvents()
}
@ -104,11 +110,9 @@ class ScannerViewModelTest {
@Test
fun `changeDeviceAddress calls radioController`() {
every { radioController.setDeviceAddress(any()) } returns Unit
viewModel.changeDeviceAddress("test_address")
dev.mokkery.verify { radioController.setDeviceAddress("test_address") }
assertEquals("test_address", radioController.lastSetDeviceAddress)
}
@Test

View file

@ -38,6 +38,7 @@ kotlin {
implementation(projects.core.data)
implementation(projects.core.database)
implementation(projects.core.datastore)
implementation(projects.core.di)
implementation(projects.core.model)
implementation(projects.core.navigation)
implementation(projects.core.network)

View file

@ -20,7 +20,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@ -42,6 +41,7 @@ import org.meshtastic.core.data.repository.FirmwareReleaseRepository
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.database.entity.FirmwareReleaseType
import org.meshtastic.core.datastore.BootloaderWarningDataSource
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.MyNodeInfo
@ -98,6 +98,7 @@ class FirmwareUpdateViewModel(
private val firmwareUpdateManager: FirmwareUpdateManager,
private val usbManager: FirmwareUsbManager,
private val fileHandler: FirmwareFileHandler,
private val dispatchers: CoroutineDispatchers,
) : ViewModel() {
private val _state = MutableStateFlow<FirmwareUpdateState>(FirmwareUpdateState.Idle)
@ -332,7 +333,7 @@ class FirmwareUpdateViewModel(
}
private suspend fun observeDfuProgress() {
firmwareUpdateManager.dfuProgressFlow().flowOn(Dispatchers.Main).collect { dfuState ->
firmwareUpdateManager.dfuProgressFlow().flowOn(dispatchers.main).collect { dfuState ->
when (dfuState) {
is DfuInternalState.Progress -> handleDfuProgress(dfuState)

View file

@ -21,6 +21,7 @@ package org.meshtastic.feature.firmware
*
* Tests firmware update flow, state management, and error handling.
*/
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class FirmwareUpdateIntegrationTest {
/*
@ -70,6 +71,11 @@ class FirmwareUpdateIntegrationTest {
firmwareUpdateManager = firmwareUpdateManager,
usbManager = usbManager,
fileHandler = fileHandler,
dispatchers = org.meshtastic.core.di.CoroutineDispatchers(
io = kotlinx.coroutines.test.UnconfinedTestDispatcher(),
main = kotlinx.coroutines.test.UnconfinedTestDispatcher(),
default = kotlinx.coroutines.test.UnconfinedTestDispatcher(),
)
)
}

View file

@ -37,13 +37,12 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.repository.UiPrefs
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.feature.map.model.CustomTileProviderConfig
import org.meshtastic.feature.map.prefs.map.GoogleMapsPrefs
import org.meshtastic.feature.map.repository.CustomTileProviderRepository
@ -56,10 +55,10 @@ class MapViewModelTest {
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 nodeRepository = FakeNodeRepository()
private val packetRepository = mock<PacketRepository>(MockMode.autofill)
private val radioConfigRepository = mock<RadioConfigRepository>(MockMode.autofill)
private val radioController = mock<RadioController>(MockMode.autofill)
private val radioController = FakeRadioController()
private val customTileProviderRepository = mock<CustomTileProviderRepository>(MockMode.autofill)
private val uiPrefs = mock<UiPrefs>(MockMode.autofill)
private val savedStateHandle = SavedStateHandle(mapOf("waypointId" to null))
@ -90,13 +89,7 @@ class MapViewModelTest {
every { customTileProviderRepository.getCustomTileProviders() } returns flowOf(emptyList())
every { radioConfigRepository.deviceProfileFlow } returns flowOf(mock(MockMode.autofill))
every { uiPrefs.theme } returns MutableStateFlow(1)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.ourNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.myId } returns MutableStateFlow(null)
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())
every { nodeRepository.getNodes() } returns flowOf(emptyList())
every { packetRepository.getWaypoints() } returns flowOf(emptyList())
every { radioController.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
viewModel =
MapViewModel(

View file

@ -29,10 +29,10 @@ 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.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.proto.ChannelSet
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
@ -45,7 +45,7 @@ class ContactsViewModelTest {
private val testDispatcher = UnconfinedTestDispatcher()
private lateinit var viewModel: ContactsViewModel
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
private val nodeRepository = FakeNodeRepository()
private val packetRepository: PacketRepository = mock(MockMode.autofill)
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
@ -54,11 +54,6 @@ class ContactsViewModelTest {
fun setUp() {
Dispatchers.setMain(testDispatcher)
every { nodeRepository.ourNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.myId } returns MutableStateFlow(null)
every { nodeRepository.getNodes() } returns MutableStateFlow(emptyList())
every { serviceRepository.connectionState } returns MutableStateFlow(ConnectionState.Disconnected)
every { packetRepository.getUnreadCountTotal() } returns MutableStateFlow(0)
every { radioConfigRepository.channelSetFlow } returns MutableStateFlow(ChannelSet())

View file

@ -129,8 +129,8 @@ class CompassViewModelTest {
}
// Bearing from (0,0) to (1,1) is approx 45 degrees
assertEquals(45f, state.bearing!!, 0.5f)
assertEquals(0f, state.heading!!, 0.1f)
assertEquals(45f, state.bearing, 0.5f)
assertEquals(0f, state.heading, 0.1f)
assertTrue(state.hasTargetPosition)
cancelAndIgnoreRemainingEvents()

View file

@ -24,9 +24,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.core.testing.FakeRadioController
import org.meshtastic.core.ui.util.AlertManager
import org.meshtastic.proto.User
import kotlin.test.Test
@ -34,9 +34,9 @@ import kotlin.test.Test
@OptIn(ExperimentalCoroutinesApi::class)
class NodeManagementActionsTest {
private val nodeRepository = mock<NodeRepository>(MockMode.autofill)
private val nodeRepository = FakeNodeRepository()
private val serviceRepository = mock<ServiceRepository>(MockMode.autofill)
private val radioController = mock<RadioController>(MockMode.autofill)
private val radioController = FakeRadioController()
private val alertManager = mock<AlertManager>(MockMode.autofill)
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)

View file

@ -23,7 +23,6 @@ import co.touchlab.kermit.Logger
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -220,12 +219,13 @@ class DebugViewModel(
private val nodeRepository: NodeRepository,
private val meshLogPrefs: MeshLogPrefs,
private val alertManager: AlertManager,
private val dispatchers: org.meshtastic.core.di.CoroutineDispatchers,
) : ViewModel() {
val meshLog: StateFlow<ImmutableList<UiMeshLog>> =
meshLogRepository
.getAllLogs()
.mapLatest { logs -> withContext(Dispatchers.Default) { toUiState(logs) } }
.mapLatest { logs -> withContext(dispatchers.default) { toUiState(logs) } }
.stateInWhileSubscribed(initialValue = persistentListOf())
private val _retentionDays = MutableStateFlow(meshLogPrefs.retentionDays.value)

View file

@ -16,27 +16,44 @@
*/
package org.meshtastic.feature.settings.debugging
import dev.mokkery.MockMode
import dev.mokkery.matcher.any
import dev.mokkery.mock
import dev.mokkery.verify
import io.kotest.matchers.shouldBe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.testing.FakeMeshLogPrefs
import org.meshtastic.core.testing.FakeMeshLogRepository
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.core.ui.util.AlertManager
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
@OptIn(ExperimentalCoroutinesApi::class)
class DebugViewModelTest {
/*
private val meshLogRepository = FakeMeshLogRepository()
private val nodeRepository = FakeNodeRepository()
private val meshLogPrefs = FakeMeshLogPrefs()
private val alertManager: AlertManager = mock(MockMode.autofill)
private val testDispatcher = UnconfinedTestDispatcher()
private val dispatchers = CoroutineDispatchers(testDispatcher, testDispatcher, testDispatcher)
private lateinit var viewModel: DebugViewModel
@Before
@BeforeTest
fun setUp() {
Dispatchers.setMain(testDispatcher)
every { meshLogRepository.getAllLogs() } returns flowOf(emptyList())
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())
every { meshLogPrefs.retentionDays.value } returns 7
every { meshLogPrefs.loggingEnabled.value } returns true
meshLogPrefs.setRetentionDays(7)
meshLogPrefs.setLoggingEnabled(true)
viewModel =
DebugViewModel(
@ -44,10 +61,11 @@ class DebugViewModelTest {
nodeRepository = nodeRepository,
meshLogPrefs = meshLogPrefs,
alertManager = alertManager,
dispatchers = dispatchers,
)
}
@After
@AfterTest
fun tearDown() {
Dispatchers.resetMain()
}
@ -56,17 +74,18 @@ class DebugViewModelTest {
fun `setRetentionDays updates prefs and deletes old logs`() = runTest {
viewModel.setRetentionDays(14)
verify { meshLogPrefs.setRetentionDays(14) }
verifySuspend { meshLogRepository.deleteLogsOlderThan(14) }
meshLogPrefs.retentionDays.value shouldBe 14
meshLogRepository.deleteLogsOlderThanCalledDays shouldBe 14
viewModel.retentionDays.value shouldBe 14
}
@Test
fun `setLoggingEnabled false deletes all logs`() = runTest {
meshLogRepository.insert(org.meshtastic.core.model.MeshLog("123", "type", 1L, "raw"))
viewModel.setLoggingEnabled(false)
verify { meshLogPrefs.setLoggingEnabled(false) }
verifySuspend { meshLogRepository.deleteAll() }
meshLogPrefs.loggingEnabled.value shouldBe false
meshLogRepository.currentLogs shouldBe emptyList()
viewModel.loggingEnabled.value shouldBe false
}
@ -91,6 +110,4 @@ class DebugViewModelTest {
viewModel.requestDeleteAllLogs()
verify { alertManager.showAlert(titleRes = any(), messageRes = any(), onConfirm = any()) }
}
*/
}

View file

@ -53,11 +53,11 @@ import org.meshtastic.core.repository.HomoglyphPrefs
import org.meshtastic.core.repository.LocationRepository
import org.meshtastic.core.repository.LocationService
import org.meshtastic.core.repository.MapConsentPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.core.repository.UiPrefs
import org.meshtastic.core.testing.FakeNodeRepository
import org.meshtastic.feature.settings.navigation.ConfigRoute
import org.meshtastic.proto.ChannelSet
import org.meshtastic.proto.ChannelSettings
@ -82,7 +82,7 @@ class RadioConfigViewModelTest {
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
private val packetRepository: PacketRepository = mock(MockMode.autofill)
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
private val nodeRepository = FakeNodeRepository()
private val locationRepository: LocationRepository = mock(MockMode.autofill)
private val mapConsentPrefs: MapConsentPrefs = mock(MockMode.autofill)
private val analyticsPrefs: AnalyticsPrefs = mock(MockMode.autofill)
@ -107,8 +107,6 @@ class RadioConfigViewModelTest {
fun setUp() {
Dispatchers.setMain(testDispatcher)
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(emptyMap())
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
every { radioConfigRepository.deviceProfileFlow } returns MutableStateFlow(DeviceProfile())
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(LocalConfig())
every { radioConfigRepository.channelSetFlow } returns MutableStateFlow(ChannelSet())
@ -153,7 +151,7 @@ class RadioConfigViewModelTest {
@Test
fun `setConfig calls useCase`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
val config = Config(device = Config.DeviceConfig(role = Config.DeviceConfig.Role.ROUTER))
@ -191,7 +189,7 @@ class RadioConfigViewModelTest {
@Test
fun `processPacketResponse updates state on metadata result`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
val packet = MeshPacket()
val metadata = DeviceMetadata(firmware_version = "3.0.0")
@ -214,7 +212,7 @@ class RadioConfigViewModelTest {
@Test
fun `updateChannels calls useCase for each changed channel`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
val old = listOf(ChannelSettings(name = "Old"))
@ -231,7 +229,7 @@ class RadioConfigViewModelTest {
@Test
fun `setResponseStateLoading for REBOOT calls useCase after packet response`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
val packetFlow = MutableSharedFlow<MeshPacket>()
every { serviceRepository.meshPacketFlow } returns packetFlow
@ -252,7 +250,7 @@ class RadioConfigViewModelTest {
@Test
fun `setResponseStateLoading for FACTORY_RESET calls useCase after packet response`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
val packetFlow = MutableSharedFlow<MeshPacket>()
every { serviceRepository.meshPacketFlow } returns packetFlow
@ -283,7 +281,7 @@ class RadioConfigViewModelTest {
@Test
fun `setOwner calls useCase`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
val user = User(long_name = "Test User")
@ -297,7 +295,7 @@ class RadioConfigViewModelTest {
@Test
fun `setRingtone calls useCase`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
everySuspend { radioConfigUseCase.setRingtone(any(), any()) } returns Unit
@ -311,7 +309,7 @@ class RadioConfigViewModelTest {
@Test
fun `setCannedMessages calls useCase`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
everySuspend { radioConfigUseCase.setCannedMessages(any(), any()) } returns Unit
@ -341,7 +339,7 @@ class RadioConfigViewModelTest {
@Test
fun `registerRequestId timeout clears request and sets error`() = runTest {
val node = Node(num = 123, user = User(id = "!123"))
every { nodeRepository.nodeDBbyNum } returns MutableStateFlow(mapOf(123 to node))
nodeRepository.setNodes(listOf(node))
viewModel = createViewModel()
everySuspend { radioConfigUseCase.getOwner(any()) } returns 42