1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
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<GpsListener>()
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<GpsPosition>()
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)
}
}
|