summaryrefslogtreecommitdiff
path: root/test-runner/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'test-runner/src/test')
-rw-r--r--test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt95
1 files changed, 95 insertions, 0 deletions
diff --git a/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt b/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt
new file mode 100644
index 0000000..44bd667
--- /dev/null
+++ b/test-runner/src/test/kotlin/org/terst/nav/track/TackDetectorTest.kt
@@ -0,0 +1,95 @@
+package org.terst.nav.track
+
+import org.junit.Assert.*
+import org.junit.Test
+
+class TackDetectorTest {
+
+ private fun pt(lat: Double, lon: Double, cog: Double, ts: Long) =
+ TrackPoint(lat, lon, 5.0, cog, 12.0, 0.0, true, ts)
+
+ private fun straightTrack(cog: Double, count: Int = 10): List<TrackPoint> =
+ (0 until count).map { i -> pt(37.0 + i * 0.01, -122.0, cog, i * 10_000L) }
+
+ private fun tackTrack(cogBefore: Double, cogAfter: Double, each: Int = 6): List<TrackPoint> {
+ val before = (0 until each).map { i -> pt(37.0 + i * 0.005, -122.0, cogBefore, i * 10_000L) }
+ val after = (0 until each).map { i -> pt(37.0 + each * 0.005 + i * 0.005, -122.0, cogAfter, (each + i) * 10_000L) }
+ return before + after
+ }
+
+ @Test fun `straight course yields no tacks`() {
+ assertTrue(TackDetector.detectTacks(straightTrack(90.0)).isEmpty())
+ }
+
+ @Test fun `straight course on port tack yields no tacks`() {
+ assertTrue(TackDetector.detectTacks(straightTrack(330.0)).isEmpty())
+ }
+
+ @Test fun `detects port to starboard tack`() {
+ val tacks = TackDetector.detectTacks(tackTrack(330.0, 70.0))
+ assertEquals(1, tacks.size)
+ assertTrue("cogBefore should be near 330", tacks[0].cogBefore in 325.0..335.0)
+ assertTrue("cogAfter should be near 70", tacks[0].cogAfter in 65.0..75.0)
+ }
+
+ @Test fun `detects starboard to port tack`() {
+ val tacks = TackDetector.detectTacks(tackTrack(70.0, 330.0))
+ assertEquals(1, tacks.size)
+ }
+
+ @Test fun `detects jibe on broad reach`() {
+ // broad reach port → starboard: 150° → 210°, delta = 60°
+ val tacks = TackDetector.detectTacks(tackTrack(150.0, 250.0))
+ assertEquals(1, tacks.size)
+ }
+
+ @Test fun `handles 0-360 wrap`() {
+ // 350° → 80°: delta = 90° through north
+ val tacks = TackDetector.detectTacks(tackTrack(350.0, 80.0))
+ assertEquals(1, tacks.size)
+ }
+
+ @Test fun `gradual course change is not a tack`() {
+ // COG increases by 5° per point: window delta ≈ 15°, below threshold
+ val points = (0 until 20).map { i -> pt(37.0 + i * 0.01, -122.0, i * 5.0, i * 10_000L) }
+ assertTrue(TackDetector.detectTacks(points).isEmpty())
+ }
+
+ @Test fun `small heading change below threshold is ignored`() {
+ // 45° change — not a tack
+ assertTrue(TackDetector.detectTacks(tackTrack(90.0, 135.0)).isEmpty())
+ }
+
+ @Test fun `very large heading change above threshold is ignored`() {
+ // 160° delta — U-turn, not a normal tack or jibe
+ assertTrue(TackDetector.detectTacks(tackTrack(90.0, 250.0)).isEmpty())
+ }
+
+ @Test fun `detects two tacks in sequence`() {
+ val leg1 = (0 until 6).map { i -> pt(37.0 + i * 0.01, -122.0, 330.0, i * 10_000L) }
+ val leg2 = (0 until 6).map { i -> pt(37.06 + i * 0.01, -122.0, 70.0, (6 + i) * 10_000L) }
+ val leg3 = (0 until 6).map { i -> pt(37.12 + i * 0.01, -122.0, 330.0, (12 + i) * 10_000L) }
+ val tacks = TackDetector.detectTacks(leg1 + leg2 + leg3)
+ assertEquals(2, tacks.size)
+ }
+
+ @Test fun `deduplicates tacks at same transition`() {
+ val before = (0 until 8).map { i -> pt(37.0, -122.0 + i * 0.0001, 330.0, i * 5_000L) }
+ val after = (0 until 8).map { i -> pt(37.0, -122.0 + 0.0008 + i * 0.0001, 70.0, (8 + i) * 5_000L) }
+ val tacks = TackDetector.detectTacks(before + after)
+ assertEquals(1, tacks.size)
+ }
+
+ @Test fun `tack event contains correct position`() {
+ val tacks = TackDetector.detectTacks(tackTrack(330.0, 70.0))
+ assertEquals(1, tacks.size)
+ // Position should be somewhere in the track bounds
+ assertTrue(tacks[0].lat in 37.0..37.1)
+ assertTrue(tacks[0].lon in -122.1..-122.0)
+ }
+
+ @Test fun `too few points returns empty`() {
+ val points = (0 until 3).map { i -> pt(37.0, -122.0, 90.0, i * 1000L) }
+ assertTrue(TackDetector.detectTacks(points).isEmpty())
+ }
+}