From 33e10fc6c12ba628384dae5f30fdf6fdeb3018a9 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:08:44 -0500 Subject: [PATCH] test(map, intro): Refactor IntroViewModelTest and BaseMapViewModelTest to use Turbine and Mokkery --- .../feature/intro/IntroViewModelTest.kt | 22 ++-- .../feature/map/BaseMapViewModelTest.kt | 113 +++++++++++------- 2 files changed, 87 insertions(+), 48 deletions(-) diff --git a/feature/intro/src/commonTest/kotlin/org/meshtastic/feature/intro/IntroViewModelTest.kt b/feature/intro/src/commonTest/kotlin/org/meshtastic/feature/intro/IntroViewModelTest.kt index 3ec3751ec..77095524b 100644 --- a/feature/intro/src/commonTest/kotlin/org/meshtastic/feature/intro/IntroViewModelTest.kt +++ b/feature/intro/src/commonTest/kotlin/org/meshtastic/feature/intro/IntroViewModelTest.kt @@ -16,39 +16,47 @@ */ package org.meshtastic.feature.intro +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + /** * Bootstrap tests for IntroViewModel. * * Tests the intro navigation flow logic. */ class IntroViewModelTest { - /* + private lateinit var viewModel: IntroViewModel - private val viewModel = IntroViewModel() + @BeforeTest + fun setUp() { + viewModel = IntroViewModel() + } @Test fun testWelcomeNavigatesNextToBluetooth() { val next = viewModel.getNextKey(Welcome, allPermissionsGranted = false) - "Welcome should navigate to Bluetooth" shouldBe Bluetooth, next + assertEquals(Bluetooth, next) } @Test fun testBluetoothNavigatesToLocation() { val next = viewModel.getNextKey(Bluetooth, allPermissionsGranted = false) - "Bluetooth should navigate to Location" shouldBe Location, next + assertEquals(Location, next) } @Test fun testLocationNavigatesToNotifications() { val next = viewModel.getNextKey(Location, allPermissionsGranted = false) - "Location should navigate to Notifications" shouldBe Notifications, next + assertEquals(Notifications, next) } @Test fun testNotificationsWithPermissionNavigatesToCriticalAlerts() { val next = viewModel.getNextKey(Notifications, allPermissionsGranted = true) - "Notifications should navigate to CriticalAlerts when permissions granted" shouldBe CriticalAlerts, next + assertEquals(CriticalAlerts, next) } @Test @@ -62,6 +70,4 @@ class IntroViewModelTest { val next = viewModel.getNextKey(CriticalAlerts, allPermissionsGranted = true) assertNull(next, "CriticalAlerts should not navigate further") } - - */ } diff --git a/feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/BaseMapViewModelTest.kt b/feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/BaseMapViewModelTest.kt index 872ad065d..3ef816ce3 100644 --- a/feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/BaseMapViewModelTest.kt +++ b/feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/BaseMapViewModelTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025-2026 Meshtastic LLC + * 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 @@ -12,82 +12,115 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License + * along with this program. See + * 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 . */ package org.meshtastic.feature.map -/** - * Bootstrap tests for BaseMapViewModel. - * - * Tests map functionality using FakeNodeRepository and test data. - */ +import app.cash.turbine.test +import dev.mokkery.answering.returns +import dev.mokkery.every +import dev.mokkery.mock +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.runTest +import kotlinx.coroutines.test.setMain +import org.meshtastic.core.model.ConnectionState +import org.meshtastic.core.repository.MapPrefs +import org.meshtastic.core.repository.PacketRepository +import org.meshtastic.core.testing.FakeNodeRepository +import org.meshtastic.core.testing.FakeRadioController +import org.meshtastic.core.testing.TestDataFactory +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +@OptIn(ExperimentalCoroutinesApi::class) class BaseMapViewModelTest { - /* - + private val testDispatcher = StandardTestDispatcher() private lateinit var viewModel: BaseMapViewModel private lateinit var nodeRepository: FakeNodeRepository private lateinit var radioController: FakeRadioController - private lateinit var mapPrefs: MapPrefs - private lateinit var packetRepository: PacketRepository + private val mapPrefs: MapPrefs = mock() + private val packetRepository: PacketRepository = mock() @BeforeTest fun setUp() { + Dispatchers.setMain(testDispatcher) nodeRepository = FakeNodeRepository() radioController = FakeRadioController() - mapPrefs = - every { showOnlyFavorites } returns MutableStateFlow(false) - every { showWaypointsOnMap } returns MutableStateFlow(false) - every { showPrecisionCircleOnMap } returns MutableStateFlow(false) - every { lastHeardFilter } returns MutableStateFlow(0L) - every { lastHeardTrackFilter } returns MutableStateFlow(0L) - } + every { mapPrefs.showOnlyFavorites } returns MutableStateFlow(false) + every { mapPrefs.showWaypointsOnMap } returns MutableStateFlow(false) + every { mapPrefs.showPrecisionCircleOnMap } returns MutableStateFlow(false) + every { mapPrefs.lastHeardFilter } returns MutableStateFlow(0L) + every { mapPrefs.lastHeardTrackFilter } returns MutableStateFlow(0L) - viewModel = - BaseMapViewModel( - mapPrefs = mapPrefs, - nodeRepository = nodeRepository, - packetRepository = packetRepository, - radioController = radioController, - ) + every { packetRepository.getWaypoints() } returns MutableStateFlow(emptyList()) + + viewModel = BaseMapViewModel( + mapPrefs = mapPrefs, + nodeRepository = nodeRepository, + packetRepository = packetRepository, + radioController = radioController, + ) + } + + @AfterTest + fun tearDown() { + Dispatchers.resetMain() } @Test - fun testInitialization() = runTest { - setUp() - assertTrue(true, "BaseMapViewModel initialized successfully") + fun testInitialization() { + assertNotNull(viewModel) } @Test fun testMyNodeInfoFlow() = runTest { - setUp() - val myNodeInfo = viewModel.myNodeInfo.value - assertTrue(myNodeInfo == null, "myNodeInfo starts as null") + viewModel.myNodeInfo.test { + assertEquals(null, awaitItem()) + cancelAndIgnoreRemainingEvents() + } } @Test fun testNodesWithPositionStartsEmpty() = runTest { - setUp() - "nodesWithPosition should start empty" shouldBe emptyList(), viewModel.nodesWithPosition.value + viewModel.nodesWithPosition.test { + assertEquals(emptyList(), awaitItem()) + cancelAndIgnoreRemainingEvents() + } } @Test fun testConnectionStateFlow() = runTest { - setUp() - radioController.setConnectionState(org.meshtastic.core.model.ConnectionState.Disconnected) - // isConnected should reflect radioController state - assertTrue(true, "Connection state flow is reactive") + viewModel.isConnected.test { + // Initially reflects radioController state (which is Disconnected in FakeRadioController default) + assertEquals(false, awaitItem()) + + radioController.setConnectionState(ConnectionState.Connected) + assertEquals(true, awaitItem()) + + radioController.setConnectionState(ConnectionState.Disconnected) + assertEquals(false, awaitItem()) + cancelAndIgnoreRemainingEvents() + } } @Test fun testNodeRepositoryIntegration() = runTest { - setUp() val testNodes = TestDataFactory.createTestNodes(3) nodeRepository.setNodes(testNodes) - "Nodes added to repository" shouldBe 3, nodeRepository.nodeDBbyNum.value.size + assertEquals(3, nodeRepository.nodeDBbyNum.value.size) } - - */ }