mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Enhance test coverage (#4847)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
1b0dc75dfe
commit
06b9f8c77a
41 changed files with 1715 additions and 502 deletions
|
|
@ -71,6 +71,8 @@ kotlin {
|
|||
commonTest.dependencies {
|
||||
implementation(kotlin("test"))
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.kotest.assertions)
|
||||
implementation(libs.kotest.property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import org.koin.core.annotation.Single
|
|||
import org.meshtastic.core.common.util.NumberFormatter
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.data.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.fullRouteDiscovery
|
||||
import org.meshtastic.core.model.getFullTracerouteResponse
|
||||
|
|
@ -34,6 +33,7 @@ import org.meshtastic.core.repository.NodeManager
|
|||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.TracerouteHandler
|
||||
import org.meshtastic.core.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
@Single
|
||||
|
|
|
|||
|
|
@ -27,22 +27,23 @@ import org.koin.core.annotation.Single
|
|||
import org.meshtastic.core.database.DatabaseProvider
|
||||
import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.repository.TracerouteSnapshotRepository
|
||||
import org.meshtastic.proto.Position
|
||||
|
||||
@Single
|
||||
class TracerouteSnapshotRepository(
|
||||
class TracerouteSnapshotRepositoryImpl(
|
||||
private val dbManager: DatabaseProvider,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
) : TracerouteSnapshotRepository {
|
||||
|
||||
fun getSnapshotPositions(logUuid: String): Flow<Map<Int, Position>> = dbManager.currentDb
|
||||
override fun getSnapshotPositions(logUuid: String): Flow<Map<Int, Position>> = dbManager.currentDb
|
||||
.flatMapLatest { it.tracerouteNodePositionDao().getByLogUuid(logUuid) }
|
||||
.distinctUntilChanged()
|
||||
.mapLatest { list -> list.associate { it.nodeNum to it.position } }
|
||||
.flowOn(dispatchers.io)
|
||||
.conflate()
|
||||
|
||||
suspend fun upsertSnapshotPositions(logUuid: String, requestId: Int, positions: Map<Int, Position>) =
|
||||
override suspend fun upsertSnapshotPositions(logUuid: String, requestId: Int, positions: Map<Int, Position>) =
|
||||
withContext(dispatchers.io) {
|
||||
val dao = dbManager.currentDb.value.tracerouteNodePositionDao()
|
||||
dao.deleteByLogUuid(logUuid)
|
||||
|
|
@ -22,6 +22,9 @@ import dev.mokkery.every
|
|||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verifySuspend
|
||||
import io.kotest.property.Arb
|
||||
import io.kotest.property.arbitrary.int
|
||||
import io.kotest.property.checkAll
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
|
|
@ -39,6 +42,7 @@ import org.meshtastic.proto.QueueStatus
|
|||
import org.meshtastic.proto.ToRadio
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class PacketHandlerImplTest {
|
||||
|
||||
|
|
@ -70,13 +74,16 @@ class PacketHandlerImplTest {
|
|||
handler.start(testScope)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInitialization() {
|
||||
assertNotNull(handler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendToRadio with ToRadio sends immediately`() {
|
||||
val toRadio = ToRadio(packet = MeshPacket(id = 123))
|
||||
|
||||
handler.sendToRadio(toRadio)
|
||||
|
||||
// No explicit assertion here in original test, but we could verify call
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -107,6 +114,17 @@ class PacketHandlerImplTest {
|
|||
testScheduler.runCurrent()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleQueueStatus property test`() = runTest(testDispatcher) {
|
||||
checkAll(Arb.int(0, 10), Arb.int(0, 32), Arb.int(0, 100000)) { res, free, packetId ->
|
||||
val status = QueueStatus(res = res, free = free, mesh_packet_id = packetId)
|
||||
|
||||
// Ensure it doesn't crash on any input
|
||||
handler.handleQueueStatus(status)
|
||||
testScheduler.runCurrent()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `outgoing packets are logged with NODE_NUM_LOCAL`() = runTest(testDispatcher) {
|
||||
val packet = MeshPacket(id = 123, decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.domain.usecase.settings
|
||||
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import org.meshtastic.core.common.UiPreferences
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
|
||||
class SetLocaleUseCaseTest {
|
||||
|
||||
private val uiPreferences: UiPreferences = mock()
|
||||
private lateinit var useCase: SetLocaleUseCase
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
useCase = SetLocaleUseCase(uiPreferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke calls setLocale on uiPreferences`() {
|
||||
every { uiPreferences.setLocale(any()) } returns Unit
|
||||
useCase("en")
|
||||
verify { uiPreferences.setLocale("en") }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.domain.usecase.settings
|
||||
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import org.meshtastic.core.repository.NotificationPrefs
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
|
||||
class SetNotificationSettingsUseCaseTest {
|
||||
|
||||
private val notificationPrefs: NotificationPrefs = mock()
|
||||
private lateinit var useCase: SetNotificationSettingsUseCase
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
useCase = SetNotificationSettingsUseCase(notificationPrefs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setMessagesEnabled calls notificationPrefs`() {
|
||||
every { notificationPrefs.setMessagesEnabled(any()) } returns Unit
|
||||
useCase.setMessagesEnabled(true)
|
||||
verify { notificationPrefs.setMessagesEnabled(true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setNodeEventsEnabled calls notificationPrefs`() {
|
||||
every { notificationPrefs.setNodeEventsEnabled(any()) } returns Unit
|
||||
useCase.setNodeEventsEnabled(false)
|
||||
verify { notificationPrefs.setNodeEventsEnabled(false) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setLowBatteryEnabled calls notificationPrefs`() {
|
||||
every { notificationPrefs.setLowBatteryEnabled(any()) } returns Unit
|
||||
useCase.setLowBatteryEnabled(true)
|
||||
verify { notificationPrefs.setLowBatteryEnabled(true) }
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +67,10 @@ kotlin {
|
|||
implementation(libs.okhttp3.logging.interceptor)
|
||||
}
|
||||
|
||||
commonTest.dependencies { implementation(libs.kotlinx.coroutines.test) }
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.kotest.assertions)
|
||||
implementation(libs.kotest.property)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.network.radio
|
||||
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import io.kotest.property.Arb
|
||||
import io.kotest.property.arbitrary.byte
|
||||
import io.kotest.property.arbitrary.byteArray
|
||||
import io.kotest.property.arbitrary.int
|
||||
import io.kotest.property.checkAll
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.meshtastic.core.network.transport.StreamFrameCodec
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class StreamInterfaceTest {
|
||||
|
||||
private val radioService: RadioInterfaceService = mock(MockMode.autofill)
|
||||
private lateinit var fakeStream: FakeStreamInterface
|
||||
|
||||
class FakeStreamInterface(service: RadioInterfaceService) : StreamInterface(service) {
|
||||
val sentBytes = mutableListOf<ByteArray>()
|
||||
|
||||
override fun sendBytes(p: ByteArray) {
|
||||
sentBytes.add(p)
|
||||
}
|
||||
|
||||
override fun flushBytes() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun keepAlive() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
fun feed(b: Byte) = readChar(b)
|
||||
|
||||
public override fun connect() = super.connect()
|
||||
}
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
every { radioService.serviceScope } returns TestScope()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handleSendToRadio property test`() = runTest {
|
||||
fakeStream = FakeStreamInterface(radioService)
|
||||
|
||||
checkAll(Arb.byteArray(Arb.int(0, 512), Arb.byte())) { payload -> fakeStream.handleSendToRadio(payload) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `readChar property test`() = runTest {
|
||||
fakeStream = FakeStreamInterface(radioService)
|
||||
|
||||
checkAll(Arb.byteArray(Arb.int(0, 100), Arb.byte())) { data ->
|
||||
data.forEach { fakeStream.feed(it) }
|
||||
// Ensure no crash
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `connect sends wake bytes`() {
|
||||
fakeStream = FakeStreamInterface(radioService)
|
||||
fakeStream.connect()
|
||||
|
||||
assertTrue(fakeStream.sentBytes.isNotEmpty())
|
||||
assertTrue(fakeStream.sentBytes[0].contentEquals(StreamFrameCodec.WAKE_BYTES))
|
||||
verify { radioService.onConnect() }
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,14 @@
|
|||
*/
|
||||
package org.meshtastic.core.network.transport
|
||||
|
||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.property.Arb
|
||||
import io.kotest.property.arbitrary.byte
|
||||
import io.kotest.property.arbitrary.byteArray
|
||||
import io.kotest.property.arbitrary.int
|
||||
import io.kotest.property.checkAll
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
|
@ -56,6 +64,31 @@ class StreamFrameCodecTest {
|
|||
assertEquals(listOf(0x55.toByte()), receivedPackets[0].toList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `frameAndSend and processInputByte are inverse`() = runTest {
|
||||
checkAll(Arb.byteArray(Arb.int(0, 512), Arb.byte())) { payload ->
|
||||
var received: ByteArray? = null
|
||||
val codec = StreamFrameCodec(onPacketReceived = { received = it })
|
||||
|
||||
val bytes = mutableListOf<ByteArray>()
|
||||
codec.frameAndSend(payload, sendBytes = { bytes.add(it) })
|
||||
|
||||
bytes.forEach { arr -> arr.forEach { codec.processInputByte(it) } }
|
||||
|
||||
received.shouldNotBeNull()
|
||||
received.shouldBe(payload)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `processInputByte is robust against random noise`() = runTest {
|
||||
checkAll(Arb.byteArray(Arb.int(0, 1000), Arb.byte())) { noise ->
|
||||
val codec = StreamFrameCodec(onPacketReceived = { /* ignore */ })
|
||||
noise.forEach { codec.processInputByte(it) }
|
||||
// Should not crash
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `processInputByte handles multiple packets sequentially`() {
|
||||
val packet1 = byteArrayOf(0x94.toByte(), 0xc3.toByte(), 0x00, 0x01, 0x11)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.meshtastic.proto.Position
|
||||
|
||||
/** Repository interface for managing snapshots of traceroute results. */
|
||||
interface TracerouteSnapshotRepository {
|
||||
/** Returns a reactive flow of positions associated with a specific traceroute log. */
|
||||
fun getSnapshotPositions(logUuid: String): Flow<Map<Int, Position>>
|
||||
|
||||
/** Persists a set of positions for a traceroute log. */
|
||||
suspend fun upsertSnapshotPositions(logUuid: String, requestId: Int, positions: Map<Int, Position>)
|
||||
}
|
||||
|
|
@ -63,6 +63,8 @@ kotlin {
|
|||
implementation(libs.junit)
|
||||
implementation(libs.kotlinx.coroutines.test)
|
||||
implementation(libs.turbine)
|
||||
implementation(libs.kotest.assertions)
|
||||
implementation(libs.kotest.property)
|
||||
}
|
||||
|
||||
androidUnitTest.dependencies { implementation(libs.androidx.test.runner) }
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ open class AlertManager {
|
|||
)
|
||||
|
||||
private val _currentAlert = MutableStateFlow<AlertData?>(null)
|
||||
val currentAlert = _currentAlert.asStateFlow()
|
||||
open val currentAlert = _currentAlert.asStateFlow()
|
||||
|
||||
fun showAlert(
|
||||
open fun showAlert(
|
||||
title: String? = null,
|
||||
titleRes: StringResource? = null,
|
||||
message: String? = null,
|
||||
|
|
@ -97,7 +97,7 @@ open class AlertManager {
|
|||
)
|
||||
}
|
||||
|
||||
fun dismissAlert() {
|
||||
open fun dismissAlert() {
|
||||
_currentAlert.value = null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.ui.emoji
|
||||
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.meshtastic.core.repository.CustomEmojiPrefs
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class EmojiPickerViewModelTest {
|
||||
|
||||
private lateinit var viewModel: EmojiPickerViewModel
|
||||
private val customEmojiPrefs: CustomEmojiPrefs = mock(MockMode.autofill)
|
||||
private val frequencyFlow = MutableStateFlow<String?>(null)
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
every { customEmojiPrefs.customEmojiFrequency } returns frequencyFlow
|
||||
viewModel = EmojiPickerViewModel(customEmojiPrefs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInitialization() {
|
||||
assertNotNull(viewModel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `customEmojiFrequency property delegates to prefs`() {
|
||||
frequencyFlow.value = "👍=10"
|
||||
assertEquals("👍=10", viewModel.customEmojiFrequency)
|
||||
|
||||
every { customEmojiPrefs.setCustomEmojiFrequency(any()) } returns Unit
|
||||
viewModel.customEmojiFrequency = "❤️=5"
|
||||
verify { customEmojiPrefs.setCustomEmojiFrequency("❤️=5") }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.ui.share
|
||||
|
||||
import app.cash.turbine.test
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.proto.SharedContact
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class SharedContactViewModelTest {
|
||||
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private lateinit var viewModel: SharedContactViewModel
|
||||
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
|
||||
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
every { nodeRepository.getNodes() } returns MutableStateFlow(emptyList())
|
||||
viewModel = SharedContactViewModel(nodeRepository, serviceRepository)
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInitialization() {
|
||||
assertNotNull(viewModel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unfilteredNodes reflects repository updates`() = runTest(testDispatcher) {
|
||||
val nodesFlow = MutableStateFlow<List<Node>>(emptyList())
|
||||
every { nodeRepository.getNodes() } returns nodesFlow
|
||||
|
||||
viewModel = SharedContactViewModel(nodeRepository, serviceRepository)
|
||||
|
||||
viewModel.unfilteredNodes.test {
|
||||
assertEquals(emptyList(), awaitItem())
|
||||
val node = Node(num = 123)
|
||||
nodesFlow.value = listOf(node)
|
||||
assertEquals(listOf(node), awaitItem())
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `addSharedContact delegates to serviceRepository`() = runTest(testDispatcher) {
|
||||
val contact = SharedContact(node_num = 123)
|
||||
everySuspend { serviceRepository.onServiceAction(any()) } returns Unit
|
||||
|
||||
val job = viewModel.addSharedContact(contact)
|
||||
job.join()
|
||||
|
||||
verifySuspend { serviceRepository.onServiceAction(ServiceAction.ImportContact(contact)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.ui.viewmodel
|
||||
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.answering.returns
|
||||
import dev.mokkery.every
|
||||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ConnectionsViewModelTest {
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
private lateinit var viewModel: ConnectionsViewModel
|
||||
private val radioConfigRepository: RadioConfigRepository = mock(MockMode.autofill)
|
||||
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
|
||||
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
|
||||
private val uiPrefs: UiPrefs = mock(MockMode.autofill)
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
|
||||
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(LocalConfig())
|
||||
every { serviceRepository.connectionState } returns
|
||||
MutableStateFlow(org.meshtastic.core.model.ConnectionState.Disconnected)
|
||||
every { nodeRepository.myNodeInfo } returns MutableStateFlow(null)
|
||||
every { nodeRepository.ourNodeInfo } returns MutableStateFlow(null)
|
||||
every { uiPrefs.hasShownNotPairedWarning } returns MutableStateFlow(false)
|
||||
|
||||
viewModel =
|
||||
ConnectionsViewModel(
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
nodeRepository = nodeRepository,
|
||||
uiPrefs = uiPrefs,
|
||||
)
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInitialization() {
|
||||
assertNotNull(viewModel)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `suppressNoPairedWarning updates state and prefs`() {
|
||||
every { uiPrefs.setHasShownNotPairedWarning(any()) } returns Unit
|
||||
|
||||
viewModel.suppressNoPairedWarning()
|
||||
|
||||
assertEquals(true, viewModel.hasShownNotPairedWarning.value)
|
||||
verify { uiPrefs.setHasShownNotPairedWarning(true) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue