package org.terst.nav.gps import org.junit.Assert.* import org.junit.Before import org.junit.Test // ── Fake implementation (no Android dependencies) ──────────────────────────── class FakeGpsProvider : GpsProvider { var currentPosition: GpsPosition? = null private val listeners = mutableListOf() var started = false override fun start() { started = true } override fun stop() { started = false } override val position: GpsPosition? get() = currentPosition override fun addListener(listener: GpsListener) { listeners.add(listener) } override fun removeListener(listener: GpsListener) { listeners.remove(listener) } fun simulatePosition(pos: GpsPosition) { currentPosition = pos listeners.forEach { it.onPositionUpdate(pos) } } fun simulateFixLost() { listeners.forEach { it.onFixLost() } } } // ── Test helpers ───────────────────────────────────────────────────────────── private fun makePosition(lat: Double = 41.0, lon: Double = -71.0, sog: Double = 5.0) = GpsPosition(lat, lon, sog, cog = 180.0, timestampMs = 1_000L) private class RecordingListener : GpsListener { val positions = mutableListOf() var fixLostCount = 0 override fun onPositionUpdate(position: GpsPosition) { positions.add(position) } override fun onFixLost() { fixLostCount++ } } // ── Tests ───────────────────────────────────────────────────────────────────── class GpsProviderTest { private lateinit var provider: FakeGpsProvider @Before fun setUp() { provider = FakeGpsProvider() } @Test fun `start sets started to true`() { provider.start() assertTrue(provider.started) } @Test fun `stop sets started to false`() { provider.start() provider.stop() assertFalse(provider.started) } @Test fun `listener receives position update`() { val listener = RecordingListener() provider.addListener(listener) val pos = makePosition() provider.simulatePosition(pos) assertEquals(1, listener.positions.size) assertEquals(pos, listener.positions[0]) } @Test fun `listener notified of fix lost`() { val listener = RecordingListener() provider.addListener(listener) provider.simulateFixLost() assertEquals(1, listener.fixLostCount) } @Test fun `multiple listeners all receive position update`() { val l1 = RecordingListener() val l2 = RecordingListener() val l3 = RecordingListener() provider.addListener(l1) provider.addListener(l2) provider.addListener(l3) provider.simulatePosition(makePosition()) assertEquals(1, l1.positions.size) assertEquals(1, l2.positions.size) assertEquals(1, l3.positions.size) } @Test fun `multiple listeners all notified of fix lost`() { val l1 = RecordingListener() val l2 = RecordingListener() provider.addListener(l1) provider.addListener(l2) provider.simulateFixLost() assertEquals(1, l1.fixLostCount) assertEquals(1, l2.fixLostCount) } @Test fun `removing listener stops notifications`() { val listener = RecordingListener() provider.addListener(listener) provider.removeListener(listener) provider.simulatePosition(makePosition()) provider.simulateFixLost() assertEquals(0, listener.positions.size) assertEquals(0, listener.fixLostCount) } @Test fun `position property reflects last simulated position`() { assertNull(provider.position) val pos = makePosition(lat = 42.5, lon = -70.0) provider.simulatePosition(pos) assertEquals(pos, provider.position) } @Test fun `SOG conversion sanity check - 1 mps is approximately 1_94384 knots`() { // 1 m/s * 1.94384 = 1.94384 knots — validate constant used in DeviceGpsProvider val knots = 1.0 * 1.94384 assertEquals(1.94384, knots, 0.00001) } }