test: fix ViewModel state collection in tests and update ALDL parser boundary validations to match byte limits

This commit is contained in:
2026-06-12 12:31:39 +01:00
parent 7c2505ab7b
commit 5bd096f54b
2 changed files with 37 additions and 35 deletions
@@ -73,19 +73,12 @@ class ALDLParserTest {
@Test
fun testRpmBoundaryValues() {
// Max valid RPM: 8000 (320 * 25 = 8000)
// Max representable RPM: 6375 (255 * 25)
val validPayload = createBasePayload().apply {
this[7] = 320.toByte() // 320 * 25 = 8000 RPM
this[7] = 255.toByte() // 255 * 25 = 6375 RPM
}
val validResult = ALDLParser.parseFrame(validPayload)
assertTrue("RPM at exactly 8000 should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
// Invalid RPM: 8001 (321 * 25 = 8025)
val invalidPayload = createBasePayload().apply {
this[7] = 321.toByte() // 321 * 25 = 8025 RPM, exceeds 8000 limit
}
val invalidResult = ALDLParser.parseFrame(invalidPayload)
assertTrue("RPM above 8000 should be rejected", invalidResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
assertTrue("Max representable RPM should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
}
@Test
@@ -121,19 +114,12 @@ class ALDLParserTest {
@Test
fun testTpsVoltageBoundaryValues() {
// Max valid: 5.1V (260 * 0.019608 = 5.098V ~ 5.1V)
// Max representable TPS: 5.0V (255 * 0.019608)
val validPayload = createBasePayload().apply {
this[8] = 260.toByte()
this[8] = 255.toByte()
}
val validResult = ALDLParser.parseFrame(validPayload)
assertTrue("TPS at 5.1V should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
// Too high: 5.2V (265 * 0.019608 = 5.196V ~ 5.2V, exceeds 5.1V)
val invalidPayload = createBasePayload().apply {
this[8] = 265.toByte()
}
val invalidResult = ALDLParser.parseFrame(invalidPayload)
assertTrue("TPS above 5.1V should be rejected", invalidResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
assertTrue("Max representable TPS should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
}
@Test
@@ -143,15 +129,19 @@ class ALDLParserTest {
val validResult = ALDLParser.parseFrame(validPayload)
assertTrue("Valid coolant temp should be accepted", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
// Too low: raw value 0 -> -40C, but below -45C limit
// Actually raw 0 gives -40C which is within bounds
// Let's test a very high raw value that gives > 220C
// raw 350 * 0.75 - 40 = 262.5 - 40 = 222.5C (exceeds 220C)
val highTempPayload = createBasePayload().apply {
this[4] = 350.toByte()
// Max representable: 255 * 0.75 - 40 = 151.25C
val maxPayload = createBasePayload().apply {
this[4] = 255.toByte()
}
val highResult = ALDLParser.parseFrame(highTempPayload)
assertTrue("Coolant temp above 220C should be rejected", highResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
val maxResult = ALDLParser.parseFrame(maxPayload)
assertTrue("Max representable coolant temp should be accepted", maxResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
// Min representable: 0 * 0.75 - 40 = -40C
val minPayload = createBasePayload().apply {
this[4] = 0.toByte()
}
val minResult = ALDLParser.parseFrame(minPayload)
assertTrue("Min representable coolant temp should be accepted", minResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
}
@Test
@@ -1,3 +1,4 @@
@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
package com.example.esp32aldldashboard.ui.main
import com.example.esp32aldldashboard.bluetooth.ConnectionState
@@ -7,21 +8,28 @@ import io.mockk.*
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
class MainScreenViewModelTest {
private val testDispatcher = UnconfinedTestDispatcher()
private val telemetryRepository = mockk<TelemetryRepository>(relaxed = true)
private val settingsRepository = mockk<SettingsRepository>(relaxed = true)
private val isCelsiusFlow = MutableStateFlow(false)
@Before
fun setUp() {
// Clear mocks
Dispatchers.setMain(testDispatcher)
clearAllMocks()
// Stub telemetry repository flows
@@ -41,6 +49,11 @@ class MainScreenViewModelTest {
}
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun testInitialStates() = runTest {
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
@@ -54,19 +67,18 @@ class MainScreenViewModelTest {
fun testToggleTemperatureUnit() = runTest {
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
// Wait for stateIn initialization
runCurrent()
// Start collecting to activate WhileSubscribed stateIn flow
backgroundScope.launch(testDispatcher) {
viewModel.isCelsius.collect {}
}
assertFalse(viewModel.isCelsius.value)
viewModel.toggleTemperatureUnit()
runCurrent() // Wait for viewmodel scope coroutine to execute settingsRepository.setIsCelsius
runCurrent() // Wait for settingsRepository update flow to propagate back through stateIn
assertTrue(viewModel.isCelsius.value)
viewModel.toggleTemperatureUnit()
runCurrent()
runCurrent()
assertFalse(viewModel.isCelsius.value)
}