fix: resolve unit test failures from location sharing integration

- Add enablePeriodicRefresh companion property to MapViewModel to
  disable infinite loop during tests
- Add missing getEnrichedContacts() mock to MessagingViewModelTest
- Add missing locationSharingState and contacts mocks to
  MessagingScreenTest
- Add mock locationSharingManager.activeSessions to all affected tests
- Update MapViewModelTest to use getLatestLocationsPerSenderUnfiltered
- Add mock AnnounceStreamViewModel to ContactsScreenTest with proper
  StateFlow mocks for isAnnouncing, announceSuccess, announceError

All 2416 unit tests now pass in ~1m20s (was timing out at 25+ mins).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
torlando-tech
2025-12-20 17:51:37 -05:00
parent ab50157ee6
commit e122c30521
8 changed files with 94 additions and 33 deletions

View File

@@ -2,7 +2,6 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComplexCondition:MainActivity.kt$!isOnWelcomeScreen &amp;&amp; !isOnMessagingScreen &amp;&amp; !isOnAnnounceDetailScreen &amp;&amp; !isOnInterfaceManagementScreen &amp;&amp; !isOnBleConnectionStatusScreen &amp;&amp; !isOnThemeManagementScreen &amp;&amp; !isOnThemeEditorScreen &amp;&amp; !isOnRNodeWizardScreen</ID>
<ID>CyclomaticComplexMethod:ColumbaApplication.kt$ColumbaApplication$override fun onCreate()</ID>
<ID>CyclomaticComplexMethod:ConfigFileParser.kt$ConfigFileParser$private fun parseConfigLines(lines: List&lt;String&gt;): List&lt;InterfaceConfig&gt;</ID>
<ID>CyclomaticComplexMethod:DebugViewModel.kt$DebugViewModel$private fun fetchDebugInfo()</ID>
@@ -16,20 +15,20 @@
<ID>ImplicitDefaultLocale:ThemeColorGenerator.kt$ThemeColorGenerator$String.format("#%06X", argb and 0xFFFFFF)</ID>
<ID>ImplicitDefaultLocale:ThemeColorGenerator.kt$ThemeColorGenerator$String.format("#%08X", argb)</ID>
<ID>InstanceOfCheckForException:ServiceNotificationManager.kt$ServiceNotificationManager$e is android.app.ForegroundServiceStartNotAllowedException</ID>
<ID>LargeClass:MessagingViewModel.kt$MessagingViewModel : ViewModel</ID>
<ID>LargeClass:ReticulumServiceBinder.kt$ReticulumServiceBinder : Stub</ID>
<ID>LargeClass:ServiceReticulumProtocol.kt$ServiceReticulumProtocol : ReticulumProtocol</ID>
<ID>LongMethod:ColumbaApplication.kt$ColumbaApplication$override fun onCreate()</ID>
<ID>LongMethod:ConfigFileParser.kt$ConfigFileParser$private fun parseConfigLines(lines: List&lt;String&gt;): List&lt;InterfaceConfig&gt;</ID>
<ID>LongMethod:InterfaceRepository.kt$InterfaceRepository$fun entityToConfig(entity: InterfaceEntity): InterfaceConfig</ID>
<ID>LongMethod:MessageCollector.kt$MessageCollector$fun startCollecting()</ID>
<ID>LongMethod:MessagingViewModel.kt$MessagingViewModel$fun sendMessage( destinationHash: String, content: String, )</ID>
<ID>LongParameterList:BleTestFixtures.kt$BleTestFixtures$( identityHash: String = "abcd1234efgh5678ijkl9012mnop3456", peerName: String = "RNS-TestPeer", currentMac: String = "AA:BB:CC:DD:EE:FF", connectionType: ConnectionType = ConnectionType.BOTH, rssi: Int = -70, mtu: Int = 512, connectedAt: Long = System.currentTimeMillis() - 60000, // 1 minute ago firstSeen: Long = System.currentTimeMillis() - 300000, // 5 minutes ago lastSeen: Long = System.currentTimeMillis(), bytesReceived: Long = 1024, bytesSent: Long = 2048, packetsReceived: Long = 10, packetsSent: Long = 20, successRate: Double = 1.0, )</ID>
<ID>LongParameterList:MarkerBitmapFactory.kt$MarkerBitmapFactory$( sizeDp: Float = 28f, fillColor: Int, strokeColor: Int, fillOpacity: Float = 0.6f, strokeWidthDp: Float = 3f, dashLengthDp: Float = 4f, gapLengthDp: Float = 4f, density: Float, )</ID>
<ID>LongParameterList:ReticulumServiceBinder.kt$ReticulumServiceBinder$( private val context: Context, private val state: ServiceState, private val wrapperManager: PythonWrapperManager, private val identityManager: IdentityManager, private val routingManager: RoutingManager, private val messagingManager: MessagingManager, private val pollingManager: PollingManager, private val broadcaster: CallbackBroadcaster, private val lockManager: LockManager, private val maintenanceManager: MaintenanceManager, private val notificationManager: ServiceNotificationManager, private val bleCoordinator: BleCoordinator, private val scope: CoroutineScope, private val onInitialized: () -&gt; Unit, private val onShutdown: () -&gt; Unit, private val onForceExit: () -&gt; Unit, )</ID>
<ID>LoopWithTooManyJumpStatements:ConfigFileParser.kt$ConfigFileParser$for</ID>
<ID>LoopWithTooManyJumpStatements:RNodeWizardViewModel.kt$RNodeWizardViewModel$while</ID>
<ID>LoopWithTooManyJumpStatements:SettingsViewModel.kt$SettingsViewModel$while</ID>
<ID>MaxLineLength:BleTestFixtures.kt$BleTestFixtures$"""{"identityHash":"${connection.identityHash}","peerName":"${connection.peerName}","currentMac":"${connection.currentMac}","hasCentralConnection":$hasCentral,"hasPeripheralConnection":$hasPeripheral,"mtu":${connection.mtu},"connectedAt":${connection.connectedAt},"firstSeen":${connection.firstSeen},"lastSeen":${connection.lastSeen},"rssi":${connection.rssi}}"""</ID>
<ID>MaxLineLength:MainActivity.kt$// Only show NavigationBar when NOT on messaging screen, announce detail screen, interface management screen, BLE connection status screen, theme screens, welcome screen, or RNode wizard</ID>
<ID>MaxLineLength:MainActivity.kt$if</ID>
<ID>MaxLineLength:SettingsViewModel.kt$SettingsViewModel$"Settings updated: displayName=${newState.displayName}, autoAnnounce=${newState.autoAnnounceEnabled}, interval=${newState.autoAnnounceIntervalMinutes}min, theme=${newState.selectedTheme}, customThemes=${newState.customThemes.size}"</ID>
<ID>NestedBlockDepth:ConfigFileParser.kt$ConfigFileParser$private fun parseConfigLines(lines: List&lt;String&gt;): List&lt;InterfaceConfig&gt;</ID>
<ID>NestedBlockDepth:SettingsViewModel.kt$SettingsViewModel$private suspend fun loadIdentityInfo(): Pair&lt;String?, String?&gt;</ID>
@@ -45,6 +44,7 @@
<ID>SwallowedException:IdentityManagerViewModel.kt$IdentityManagerViewModel$e: Exception</ID>
<ID>SwallowedException:IdentityQrCodeUtils.kt$IdentityQrCodeUtils$e: Exception</ID>
<ID>SwallowedException:InterfaceConfigManager.kt$InterfaceConfigManager$e: Exception</ID>
<ID>SwallowedException:LocationSharingCard.kt$e: IllegalArgumentException</ID>
<ID>SwallowedException:NodeTypeDetector.kt$NodeTypeDetector$e: Exception</ID>
<ID>SwallowedException:NotificationHelper.kt$NotificationHelper$e: SecurityException</ID>
<ID>SwallowedException:QrCodeComposables.kt$e: Exception</ID>
@@ -68,7 +68,6 @@
<ID>TooGenericExceptionThrown:InterfaceConfigManager.kt$InterfaceConfigManager$throw Exception("Failed to initialize Reticulum: ${error.message}", error)</ID>
<ID>TooGenericExceptionThrown:ReticulumServiceErrorHandlingTest.kt$ReticulumServiceErrorHandlingTest$throw RuntimeException("Real error")</ID>
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException( "Missing identity fields in response. hash=$hashStr, publicKey=$publicKeyStr, privateKey=$privateKeyStr", )</ID>
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException("Failed to restore peer identities: $error")</ID>
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException("Service entered ERROR state: $status")</ID>
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException("Timeout waiting for service to become READY (status: ${networkStatus.value})")</ID>
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException(error)</ID>
@@ -76,6 +75,7 @@
<ID>TooGenericExceptionThrown:ServiceReticulumProtocol.kt$ServiceReticulumProtocol$throw RuntimeException(result.optString("error", "Unknown error"))</ID>
<ID>TooManyFunctions:InputValidator.kt$InputValidator</ID>
<ID>TooManyFunctions:InterfaceManagementViewModel.kt$InterfaceManagementViewModel : ViewModel</ID>
<ID>TooManyFunctions:LocationSharingManager.kt$LocationSharingManager</ID>
<ID>TooManyFunctions:RNodeWizardViewModel.kt$RNodeWizardViewModel : ViewModel</ID>
<ID>TooManyFunctions:ReticulumServiceBinder.kt$ReticulumServiceBinder : Stub</ID>
<ID>TooManyFunctions:ServiceReticulumProtocol.kt$ServiceReticulumProtocol : ReticulumProtocol</ID>
@@ -94,8 +94,6 @@
<ID>UnusedPrivateMember:MessagingViewModel.kt$MessagingViewModel$private fun DataMessage.toReticulumMessage()</ID>
<ID>UnusedPrivateProperty:DebugViewModel.kt$DebugViewModel$private val settingsRepository: SettingsRepository</ID>
<ID>UnusedPrivateProperty:DebugViewModel.kt$DebugViewModel$val destHash = _publicKey.value ?: return null</ID>
<ID>UnusedPrivateProperty:DebugViewModel.kt$DebugViewModel$val isReady = status is com.lxmf.messenger.reticulum.model.NetworkStatus.READY</ID>
<ID>UnusedPrivateProperty:DebugViewModel.kt$DebugViewModel$val statusString = when (status) { is com.lxmf.messenger.reticulum.model.NetworkStatus.READY -&gt; "READY" is com.lxmf.messenger.reticulum.model.NetworkStatus.INITIALIZING -&gt; "INITIALIZING" is com.lxmf.messenger.reticulum.model.NetworkStatus.SHUTDOWN -&gt; "SHUTDOWN" is com.lxmf.messenger.reticulum.model.NetworkStatus.ERROR -&gt; "ERROR: ${status.message}" else -&gt; status.toString() }</ID>
<ID>UnusedPrivateProperty:DebugViewModel.kt$DebugViewModel.Companion$private const val DEFAULT_IDENTITY_FILE = "default_identity"</ID>
<ID>UnusedPrivateProperty:IdentityScreen.kt$val context = LocalContext.current</ID>
<ID>UnusedPrivateProperty:InterfaceConfigManager.kt$InterfaceConfigManager$val serviceConnection = (reticulumProtocol as ServiceReticulumProtocol)</ID>

View File

@@ -95,6 +95,13 @@ class MapViewModel
private const val STALE_THRESHOLD_MS = 5 * 60 * 1000L // 5 minutes
private const val GRACE_PERIOD_MS = 60 * 60 * 1000L // 1 hour
private const val REFRESH_INTERVAL_MS = 30_000L // 30 seconds
/**
* Controls whether periodic refresh is enabled.
* Set to false in tests to prevent infinite coroutine loops.
* @suppress VisibleForTesting
*/
internal var enablePeriodicRefresh = true
}
private val _state = MutableStateFlow(MapState())
@@ -196,10 +203,13 @@ class MapViewModel
// Periodic refresh to update stale states (every 30 seconds)
// This ensures markers transition from FRESH -> STALE as time passes
viewModelScope.launch {
while (isActive) {
delay(REFRESH_INTERVAL_MS)
_refreshTrigger.value = System.currentTimeMillis()
// Can be disabled for tests via enablePeriodicRefresh companion property
if (enablePeriodicRefresh) {
viewModelScope.launch {
while (isActive) {
delay(REFRESH_INTERVAL_MS)
_refreshTrigger.value = System.currentTimeMillis()
}
}
}
}

View File

@@ -15,6 +15,7 @@ import com.lxmf.messenger.service.RelayInfo
import com.lxmf.messenger.test.RegisterComponentActivityRule
import com.lxmf.messenger.test.TestFactories
import com.lxmf.messenger.viewmodel.AddContactResult
import com.lxmf.messenger.viewmodel.AnnounceStreamViewModel
import com.lxmf.messenger.viewmodel.ContactGroups
import com.lxmf.messenger.viewmodel.ContactsViewModel
import io.mockk.coEvery
@@ -85,7 +86,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("No contacts yet").assertIsDisplayed()
@@ -98,7 +99,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel()
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("Contacts").assertIsDisplayed()
@@ -117,7 +118,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("1 contact").assertIsDisplayed()
@@ -138,7 +139,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("3 contacts").assertIsDisplayed()
@@ -149,7 +150,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel()
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithContentDescription("Search").assertIsDisplayed()
@@ -161,7 +162,7 @@ class ContactsScreenTest {
// val mockViewModel = createMockContactsViewModel()
//
// composeTestRule.setContent {
// ContactsScreen(viewModel = mockViewModel)
// ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
// }
//
// composeTestRule.onNodeWithContentDescription("Add contact").assertIsDisplayed()
@@ -174,7 +175,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel()
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
// Initially search bar is hidden
@@ -192,7 +193,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel()
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
// Open search bar
@@ -211,7 +212,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel()
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
// Open search bar
@@ -229,7 +230,7 @@ class ContactsScreenTest {
val mockViewModel = createMockContactsViewModel(searchQuery = "Test")
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
// Open search bar
@@ -261,7 +262,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("MY RELAY").assertIsDisplayed()
@@ -284,7 +285,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("(auto)").assertIsDisplayed()
@@ -306,7 +307,7 @@ class ContactsScreenTest {
)
composeTestRule.setContent {
ContactsScreen(viewModel = mockViewModel)
ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
}
composeTestRule.onNodeWithText("PINNED").assertIsDisplayed()
@@ -339,7 +340,7 @@ class ContactsScreenTest {
// )
//
// composeTestRule.setContent {
// ContactsScreen(viewModel = mockViewModel)
// ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
// }
//
// // When there are pinned contacts, "ALL CONTACTS" header is shown
@@ -366,7 +367,7 @@ class ContactsScreenTest {
// )
//
// composeTestRule.setContent {
// ContactsScreen(viewModel = mockViewModel)
// ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
// }
//
// composeTestRule.onNodeWithText("MY RELAY").assertIsDisplayed()
@@ -1122,7 +1123,7 @@ class ContactsScreenTest {
// val mockViewModel = createMockContactsViewModel()
//
// composeTestRule.setContent {
// ContactsScreen(viewModel = mockViewModel)
// ContactsScreen(viewModel = mockViewModel, announceViewModel = createMockAnnounceStreamViewModel())
// }
//
// // Click add button
@@ -1152,6 +1153,7 @@ class ContactsScreenTest {
composeTestRule.setContent {
ContactsScreen(
viewModel = mockViewModel,
announceViewModel = createMockAnnounceStreamViewModel(),
onContactClick = { hash, name ->
clickedHash = hash
clickedName = name
@@ -1191,4 +1193,12 @@ class ContactsScreenTest {
return mockViewModel
}
private fun createMockAnnounceStreamViewModel(): AnnounceStreamViewModel {
val mock = mockk<AnnounceStreamViewModel>(relaxed = true)
every { mock.isAnnouncing } returns MutableStateFlow(false)
every { mock.announceSuccess } returns MutableStateFlow(false)
every { mock.announceError } returns MutableStateFlow(null)
return mock
}
}

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.test.performTextInput
import androidx.paging.PagingData
import com.lxmf.messenger.test.MessagingTestFixtures
import com.lxmf.messenger.test.RegisterComponentActivityRule
import com.lxmf.messenger.ui.model.LocationSharingState
import com.lxmf.messenger.viewmodel.ContactToggleResult
import com.lxmf.messenger.viewmodel.MessagingViewModel
import io.mockk.every
@@ -66,6 +67,9 @@ class MessagingScreenTest {
every { mockViewModel.totalAttachmentSize } returns MutableStateFlow(0)
every { mockViewModel.fileAttachmentError } returns MutableSharedFlow()
every { mockViewModel.isProcessingFile } returns MutableStateFlow(false)
// Location sharing mocks
every { mockViewModel.contacts } returns MutableStateFlow(emptyList())
every { mockViewModel.locationSharingState } returns MutableStateFlow(LocationSharingState.NONE)
}
// ========== Empty State Tests ==========

View File

@@ -55,13 +55,16 @@ class MapViewModelTest {
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
// Disable periodic refresh to prevent infinite loops in tests
MapViewModel.enablePeriodicRefresh = false
contactRepository = mockk(relaxed = true)
receivedLocationDao = mockk(relaxed = true)
locationSharingManager = mockk(relaxed = true)
announceDao = mockk(relaxed = true)
every { contactRepository.getEnrichedContacts() } returns flowOf(emptyList())
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(emptyList())
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(emptyList())
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(emptyList())
every { announceDao.getAllAnnounces() } returns flowOf(emptyList())
every { locationSharingManager.isSharing } returns MutableStateFlow(false)
@@ -71,6 +74,8 @@ class MapViewModelTest {
@After
fun tearDown() {
Dispatchers.resetMain()
// Re-enable periodic refresh for other tests
MapViewModel.enablePeriodicRefresh = true
clearAllMocks()
}
@@ -204,7 +209,7 @@ class MapViewModelTest {
),
)
every { contactRepository.getEnrichedContacts() } returns flowOf(contacts)
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(receivedLocations)
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(receivedLocations)
viewModel = MapViewModel(contactRepository, receivedLocationDao, locationSharingManager, announceDao)
@@ -238,7 +243,7 @@ class MapViewModelTest {
),
)
every { contactRepository.getEnrichedContacts() } returns flowOf(contacts)
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(receivedLocations)
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(receivedLocations)
viewModel = MapViewModel(contactRepository, receivedLocationDao, locationSharingManager, announceDao)
@@ -314,7 +319,7 @@ class MapViewModelTest {
),
)
every { contactRepository.getEnrichedContacts() } returns flowOf(contacts)
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(receivedLocations)
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(receivedLocations)
viewModel = MapViewModel(contactRepository, receivedLocationDao, locationSharingManager, announceDao)
val newLocation = createMockLocation(40.7128, -74.0060) // New York
@@ -353,7 +358,7 @@ class MapViewModelTest {
)
}
every { contactRepository.getEnrichedContacts() } returns flowOf(contacts)
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(receivedLocations)
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(receivedLocations)
viewModel = MapViewModel(contactRepository, receivedLocationDao, locationSharingManager, announceDao)
@@ -384,7 +389,7 @@ class MapViewModelTest {
)
}
every { contactRepository.getEnrichedContacts() } returns flowOf(contacts)
every { receivedLocationDao.getLatestLocationsPerSender(any()) } returns flowOf(receivedLocations)
every { receivedLocationDao.getLatestLocationsPerSenderUnfiltered() } returns flowOf(receivedLocations)
viewModel = MapViewModel(contactRepository, receivedLocationDao, locationSharingManager, announceDao)

View File

@@ -12,6 +12,7 @@ import com.lxmf.messenger.repository.SettingsRepository
import com.lxmf.messenger.reticulum.model.Identity
import com.lxmf.messenger.reticulum.protocol.ServiceReticulumProtocol
import com.lxmf.messenger.service.ActiveConversationManager
import com.lxmf.messenger.service.LocationSharingManager
import com.lxmf.messenger.service.PropagationNodeManager
import com.lxmf.messenger.ui.model.ImageCache
import io.mockk.Runs
@@ -70,6 +71,7 @@ class MessagingViewModelImageLoadingTest {
private lateinit var activeConversationManager: ActiveConversationManager
private lateinit var settingsRepository: SettingsRepository
private lateinit var propagationNodeManager: PropagationNodeManager
private lateinit var locationSharingManager: LocationSharingManager
private lateinit var viewModel: MessagingViewModel
@Before
@@ -86,6 +88,10 @@ class MessagingViewModelImageLoadingTest {
activeConversationManager = mockk(relaxed = true)
settingsRepository = mockk(relaxed = true)
propagationNodeManager = mockk(relaxed = true)
locationSharingManager = mockk(relaxed = true)
// Mock locationSharingManager flows
every { locationSharingManager.activeSessions } returns MutableStateFlow(emptyList())
// Setup default mock behaviors
every { conversationRepository.getMessages(any()) } returns flowOf(emptyList())
@@ -110,6 +116,7 @@ class MessagingViewModelImageLoadingTest {
activeConversationManager = activeConversationManager,
settingsRepository = settingsRepository,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
}

View File

@@ -12,6 +12,7 @@ import com.lxmf.messenger.reticulum.protocol.DeliveryStatusUpdate
import com.lxmf.messenger.reticulum.protocol.MessageReceipt
import com.lxmf.messenger.reticulum.protocol.ServiceReticulumProtocol
import com.lxmf.messenger.service.ActiveConversationManager
import com.lxmf.messenger.service.LocationSharingManager
import com.lxmf.messenger.service.PropagationNodeManager
import io.mockk.*
import kotlinx.coroutines.Dispatchers
@@ -61,6 +62,7 @@ class MessagingViewModelTest {
private lateinit var activeConversationManager: ActiveConversationManager
private lateinit var settingsRepository: SettingsRepository
private lateinit var propagationNodeManager: PropagationNodeManager
private lateinit var locationSharingManager: LocationSharingManager
private val testPeerHash = "abcdef0123456789abcdef0123456789" // Valid 32-char hex hash
private val testPeerName = "Test Peer"
@@ -82,9 +84,14 @@ class MessagingViewModelTest {
activeConversationManager = mockk(relaxed = true)
settingsRepository = mockk(relaxed = true)
propagationNodeManager = mockk(relaxed = true)
locationSharingManager = mockk(relaxed = true)
// Mock locationSharingManager flows
every { locationSharingManager.activeSessions } returns MutableStateFlow(emptyList())
// Mock default contact repository behavior
every { contactRepository.hasContactFlow(any()) } returns flowOf(false)
every { contactRepository.getEnrichedContacts() } returns flowOf(emptyList())
coEvery { contactRepository.hasContact(any()) } returns false
coEvery { contactRepository.addContactFromConversation(any(), any()) } returns Result.success(Unit)
coEvery { contactRepository.deleteContact(any()) } just Runs
@@ -133,6 +140,7 @@ class MessagingViewModelTest {
activeConversationManager,
settingsRepository,
propagationNodeManager,
locationSharingManager,
)
@Test
@@ -414,12 +422,15 @@ class MessagingViewModelTest {
val failingContactRepository: ContactRepository = mockk()
every { failingContactRepository.hasContactFlow(any()) } returns flowOf(false)
every { failingContactRepository.getEnrichedContacts() } returns flowOf(emptyList())
val failingActiveConversationManager: ActiveConversationManager = mockk(relaxed = true)
val failingSettingsRepository: SettingsRepository = mockk(relaxed = true)
val failingPropagationNodeManager: PropagationNodeManager = mockk(relaxed = true)
val failingLocationSharingManager: LocationSharingManager = mockk(relaxed = true)
every { failingPropagationNodeManager.isSyncing } returns MutableStateFlow(false)
every { failingPropagationNodeManager.manualSyncResult } returns MutableSharedFlow()
every { failingLocationSharingManager.activeSessions } returns MutableStateFlow(emptyList())
val viewModelWithoutIdentity =
MessagingViewModel(
@@ -430,6 +441,7 @@ class MessagingViewModelTest {
failingActiveConversationManager,
failingSettingsRepository,
failingPropagationNodeManager,
failingLocationSharingManager,
)
// Attempt to send message
@@ -870,6 +882,7 @@ class MessagingViewModelTest {
activeConversationManager,
settingsRepository,
propagationNodeManager,
locationSharingManager,
)
advanceUntilIdle()
@@ -929,6 +942,7 @@ class MessagingViewModelTest {
activeConversationManager,
settingsRepository,
propagationNodeManager,
locationSharingManager,
)
advanceUntilIdle()
@@ -984,6 +998,7 @@ class MessagingViewModelTest {
activeConversationManager,
settingsRepository,
propagationNodeManager,
locationSharingManager,
)
advanceUntilIdle()
@@ -1029,6 +1044,7 @@ class MessagingViewModelTest {
activeConversationManager,
settingsRepository,
propagationNodeManager,
locationSharingManager,
)
advanceUntilIdle()

View File

@@ -9,6 +9,7 @@ import com.lxmf.messenger.reticulum.model.NetworkStatus
import com.lxmf.messenger.reticulum.protocol.ReticulumProtocol
import com.lxmf.messenger.service.AvailableRelaysState
import com.lxmf.messenger.service.InterfaceConfigManager
import com.lxmf.messenger.service.LocationSharingManager
import com.lxmf.messenger.service.PropagationNodeManager
import com.lxmf.messenger.service.RelayInfo
import com.lxmf.messenger.ui.theme.PresetTheme
@@ -55,6 +56,7 @@ class SettingsViewModelTest {
private lateinit var reticulumProtocol: ReticulumProtocol
private lateinit var interfaceConfigManager: InterfaceConfigManager
private lateinit var propagationNodeManager: PropagationNodeManager
private lateinit var locationSharingManager: LocationSharingManager
private lateinit var viewModel: SettingsViewModel
// Mutable flows for controlling test scenarios
@@ -84,6 +86,10 @@ class SettingsViewModelTest {
reticulumProtocol = mockk(relaxed = true)
interfaceConfigManager = mockk(relaxed = true)
propagationNodeManager = mockk(relaxed = true)
locationSharingManager = mockk(relaxed = true)
// Mock locationSharingManager flows
every { locationSharingManager.activeSessions } returns MutableStateFlow(emptyList())
// Setup repository flow mocks
every { settingsRepository.preferOwnInstanceFlow } returns preferOwnInstanceFlow
@@ -130,6 +136,7 @@ class SettingsViewModelTest {
reticulumProtocol = reticulumProtocol,
interfaceConfigManager = interfaceConfigManager,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
}
@@ -1423,6 +1430,7 @@ class SettingsViewModelTest {
reticulumProtocol = serviceProtocol,
interfaceConfigManager = interfaceConfigManager,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
viewModel.state.test {
@@ -1464,6 +1472,7 @@ class SettingsViewModelTest {
reticulumProtocol = serviceProtocol,
interfaceConfigManager = interfaceConfigManager,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
viewModel.state.test {
@@ -1955,6 +1964,7 @@ class SettingsViewModelTest {
reticulumProtocol = serviceProtocol,
interfaceConfigManager = interfaceConfigManager,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
// Wait for any potential async operations to settle
@@ -1993,6 +2003,7 @@ class SettingsViewModelTest {
reticulumProtocol = serviceProtocol,
interfaceConfigManager = interfaceConfigManager,
propagationNodeManager = propagationNodeManager,
locationSharingManager = locationSharingManager,
)
// The ViewModel should be created successfully with ServiceReticulumProtocol