test: fix ViewModel state collection in tests and update ALDL parser boundary validations to match byte limits
This commit is contained in:
@@ -73,19 +73,12 @@ class ALDLParserTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testRpmBoundaryValues() {
|
fun testRpmBoundaryValues() {
|
||||||
// Max valid RPM: 8000 (320 * 25 = 8000)
|
// Max representable RPM: 6375 (255 * 25)
|
||||||
val validPayload = createBasePayload().apply {
|
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)
|
val validResult = ALDLParser.parseFrame(validPayload)
|
||||||
assertTrue("RPM at exactly 8000 should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
assertTrue("Max representable RPM 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -121,19 +114,12 @@ class ALDLParserTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTpsVoltageBoundaryValues() {
|
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 {
|
val validPayload = createBasePayload().apply {
|
||||||
this[8] = 260.toByte()
|
this[8] = 255.toByte()
|
||||||
}
|
}
|
||||||
val validResult = ALDLParser.parseFrame(validPayload)
|
val validResult = ALDLParser.parseFrame(validPayload)
|
||||||
assertTrue("TPS at 5.1V should be valid", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
assertTrue("Max representable TPS 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -143,15 +129,19 @@ class ALDLParserTest {
|
|||||||
val validResult = ALDLParser.parseFrame(validPayload)
|
val validResult = ALDLParser.parseFrame(validPayload)
|
||||||
assertTrue("Valid coolant temp should be accepted", validResult is com.example.esp32aldldashboard.parser.ALDLParseResult.Success)
|
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
|
// Max representable: 255 * 0.75 - 40 = 151.25C
|
||||||
// Actually raw 0 gives -40C which is within bounds
|
val maxPayload = createBasePayload().apply {
|
||||||
// Let's test a very high raw value that gives > 220C
|
this[4] = 255.toByte()
|
||||||
// raw 350 * 0.75 - 40 = 262.5 - 40 = 222.5C (exceeds 220C)
|
|
||||||
val highTempPayload = createBasePayload().apply {
|
|
||||||
this[4] = 350.toByte()
|
|
||||||
}
|
}
|
||||||
val highResult = ALDLParser.parseFrame(highTempPayload)
|
val maxResult = ALDLParser.parseFrame(maxPayload)
|
||||||
assertTrue("Coolant temp above 220C should be rejected", highResult is com.example.esp32aldldashboard.parser.ALDLParseResult.InvalidData)
|
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
|
@Test
|
||||||
|
|||||||
+19
-7
@@ -1,3 +1,4 @@
|
|||||||
|
@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||||
package com.example.esp32aldldashboard.ui.main
|
package com.example.esp32aldldashboard.ui.main
|
||||||
|
|
||||||
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
import com.example.esp32aldldashboard.bluetooth.ConnectionState
|
||||||
@@ -7,21 +8,28 @@ import io.mockk.*
|
|||||||
import junit.framework.TestCase.assertEquals
|
import junit.framework.TestCase.assertEquals
|
||||||
import junit.framework.TestCase.assertFalse
|
import junit.framework.TestCase.assertFalse
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class MainScreenViewModelTest {
|
class MainScreenViewModelTest {
|
||||||
|
|
||||||
|
private val testDispatcher = UnconfinedTestDispatcher()
|
||||||
private val telemetryRepository = mockk<TelemetryRepository>(relaxed = true)
|
private val telemetryRepository = mockk<TelemetryRepository>(relaxed = true)
|
||||||
private val settingsRepository = mockk<SettingsRepository>(relaxed = true)
|
private val settingsRepository = mockk<SettingsRepository>(relaxed = true)
|
||||||
private val isCelsiusFlow = MutableStateFlow(false)
|
private val isCelsiusFlow = MutableStateFlow(false)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
// Clear mocks
|
Dispatchers.setMain(testDispatcher)
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
|
|
||||||
// Stub telemetry repository flows
|
// Stub telemetry repository flows
|
||||||
@@ -41,6 +49,11 @@ class MainScreenViewModelTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testInitialStates() = runTest {
|
fun testInitialStates() = runTest {
|
||||||
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
|
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
|
||||||
@@ -54,19 +67,18 @@ class MainScreenViewModelTest {
|
|||||||
fun testToggleTemperatureUnit() = runTest {
|
fun testToggleTemperatureUnit() = runTest {
|
||||||
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
|
val viewModel = MainScreenViewModel(telemetryRepository, settingsRepository)
|
||||||
|
|
||||||
// Wait for stateIn initialization
|
// Start collecting to activate WhileSubscribed stateIn flow
|
||||||
runCurrent()
|
backgroundScope.launch(testDispatcher) {
|
||||||
|
viewModel.isCelsius.collect {}
|
||||||
|
}
|
||||||
|
|
||||||
assertFalse(viewModel.isCelsius.value)
|
assertFalse(viewModel.isCelsius.value)
|
||||||
|
|
||||||
viewModel.toggleTemperatureUnit()
|
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)
|
assertTrue(viewModel.isCelsius.value)
|
||||||
|
|
||||||
viewModel.toggleTemperatureUnit()
|
viewModel.toggleTemperatureUnit()
|
||||||
runCurrent()
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
assertFalse(viewModel.isCelsius.value)
|
assertFalse(viewModel.isCelsius.value)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user