summaryrefslogtreecommitdiff
path: root/android-app/app/src/test/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'android-app/app/src/test/kotlin')
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/data/repository/WeatherRepositoryTest.kt31
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/track/GpxRoundTripTest.kt83
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/track/TrackSummaryTest.kt56
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/ui/DirectionArrowViewTest.kt25
-rw-r--r--android-app/app/src/test/kotlin/org/terst/nav/ui/InstrumentHandlerTest.kt39
5 files changed, 228 insertions, 6 deletions
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/data/repository/WeatherRepositoryTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/data/repository/WeatherRepositoryTest.kt
index 749630f..c455085 100644
--- a/android-app/app/src/test/kotlin/org/terst/nav/data/repository/WeatherRepositoryTest.kt
+++ b/android-app/app/src/test/kotlin/org/terst/nav/data/repository/WeatherRepositoryTest.kt
@@ -3,8 +3,7 @@ package org.terst.nav.data.repository
import org.terst.nav.data.api.MarineApiService
import org.terst.nav.data.api.WeatherApiService
import org.terst.nav.data.model.*
-import io.mockk.coEvery
-import io.mockk.mockk
+import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.Assert.*
import org.junit.Before
@@ -36,6 +35,9 @@ class WeatherRepositoryTest {
time = listOf("2026-03-13T00:00", "2026-03-13T01:00"),
waveHeight = listOf(1.2, 1.1),
waveDirection = listOf(250.0, 255.0),
+ swellWaveHeight = emptyList(),
+ swellWaveDirection = emptyList(),
+ swellWavePeriod = emptyList(),
oceanCurrentVelocity = listOf(0.3, 0.4),
oceanCurrentDirection = listOf(180.0, 185.0)
)
@@ -48,7 +50,7 @@ class WeatherRepositoryTest {
@Test
fun `fetchForecastItems maps weather response to ForecastItem list`() = runTest {
- coEvery { weatherApi.getWeatherForecast(any(), any()) } returns weatherResponse
+ coEvery { weatherApi.getWeatherForecast(any(), any(), any(), any(), any()) } returns weatherResponse
coEvery { marineApi.getMarineForecast(any(), any()) } returns marineResponse
val result = repo.fetchForecastItems(37.5, -122.3)
@@ -65,8 +67,25 @@ class WeatherRepositoryTest {
}
@Test
+ fun `fetchCurrentConditions maps responses to MarineConditions`() = runTest {
+ coEvery { weatherApi.getWeatherForecast(any(), any(), any(), eq(1), any()) } returns weatherResponse
+ coEvery { marineApi.getMarineForecast(any(), any()) } returns marineResponse
+
+ val result = repo.fetchCurrentConditions(37.5, -122.3)
+
+ if (result.isFailure) {
+ fail("fetchCurrentConditions failed with: ${result.exceptionOrNull()}")
+ }
+ val cond = result.getOrThrow()
+ assertEquals(15.0, cond.windSpeedKt!!, 0.001)
+ assertEquals(1.2, cond.waveHeightM!!, 0.001)
+ assertEquals(0.3 * 1.94384, cond.currentSpeedKt!!, 0.001)
+ assertEquals(180.0, cond.currentDirDeg!!, 0.001)
+ }
+
+ @Test
fun `fetchWindArrow returns WindArrow for first (current) hour`() = runTest {
- coEvery { weatherApi.getWeatherForecast(any(), any()) } returns weatherResponse
+ coEvery { weatherApi.getWeatherForecast(any(), any(), any(), eq(1), any()) } returns weatherResponse
coEvery { marineApi.getMarineForecast(any(), any()) } returns marineResponse
val result = repo.fetchWindArrow(37.5, -122.3)
@@ -81,7 +100,7 @@ class WeatherRepositoryTest {
@Test
fun `fetchForecastItems returns failure when weather API throws`() = runTest {
- coEvery { weatherApi.getWeatherForecast(any(), any()) } throws RuntimeException("Network error")
+ coEvery { weatherApi.getWeatherForecast(any(), any(), any(), any(), any()) } throws RuntimeException("Network error")
coEvery { marineApi.getMarineForecast(any(), any()) } returns marineResponse
val result = repo.fetchForecastItems(37.5, -122.3)
@@ -91,7 +110,7 @@ class WeatherRepositoryTest {
@Test
fun `fetchWindArrow returns failure when API throws`() = runTest {
- coEvery { weatherApi.getWeatherForecast(any(), any()) } throws RuntimeException("Timeout")
+ coEvery { weatherApi.getWeatherForecast(any(), any(), any(), eq(1), any()) } throws RuntimeException("Timeout")
coEvery { marineApi.getMarineForecast(any(), any()) } returns marineResponse
val result = repo.fetchWindArrow(37.5, -122.3)
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/track/GpxRoundTripTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/track/GpxRoundTripTest.kt
new file mode 100644
index 0000000..7ed7ec7
--- /dev/null
+++ b/android-app/app/src/test/kotlin/org/terst/nav/track/GpxRoundTripTest.kt
@@ -0,0 +1,83 @@
+package org.terst.nav.track
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class GpxRoundTripTest {
+
+ private fun roundTrip(points: List<TrackPoint>): List<TrackPoint> {
+ val gpx = GpxSerializer.serialize(points, "Test Track")
+ return GpxParser.parse(gpx.byteInputStream())
+ }
+
+ @Test
+ fun `round-trip preserves lat and lon`() {
+ val pt = TrackPoint(lat = 37.8044, lon = -122.2712, sogKnots = 6.1, cogDeg = 247.0)
+ val result = roundTrip(listOf(pt)).first()
+ assertEquals(37.8044, result.lat, 0.00001)
+ assertEquals(-122.2712, result.lon, 0.00001)
+ }
+
+ @Test
+ fun `round-trip preserves sog and cog`() {
+ val pt = TrackPoint(lat = 0.0, lon = 0.0, sogKnots = 5.3, cogDeg = 183.0)
+ val result = roundTrip(listOf(pt)).first()
+ assertEquals(5.3, result.sogKnots, 0.001)
+ assertEquals(183.0, result.cogDeg, 0.001)
+ }
+
+ @Test
+ fun `round-trip preserves optional nav fields`() {
+ val pt = TrackPoint(
+ lat = 1.0, lon = 2.0, sogKnots = 4.0, cogDeg = 90.0,
+ depthMeters = 12.5, baroHpa = 1013.2, windSpeedKnots = 14.0,
+ windAngleDeg = 45.0, isTrueWind = true, waveHeightM = 1.2
+ )
+ val result = roundTrip(listOf(pt)).first()
+ assertEquals(12.5, result.depthMeters!!, 0.001)
+ assertEquals(1013.2, result.baroHpa!!, 0.001)
+ assertEquals(14.0, result.windSpeedKnots!!, 0.001)
+ assertEquals(45.0, result.windAngleDeg!!, 0.001)
+ assertTrue(result.isTrueWind)
+ assertEquals(1.2, result.waveHeightM!!, 0.001)
+ }
+
+ @Test
+ fun `round-trip with null optional fields leaves them null`() {
+ val pt = TrackPoint(lat = 0.0, lon = 0.0, sogKnots = 0.0, cogDeg = 0.0)
+ val result = roundTrip(listOf(pt)).first()
+ assertNull(result.depthMeters)
+ assertNull(result.baroHpa)
+ assertNull(result.windSpeedKnots)
+ }
+
+ @Test
+ fun `round-trip preserves multiple points in order`() {
+ val points = listOf(
+ TrackPoint(lat = 1.0, lon = 1.0, sogKnots = 1.0, cogDeg = 0.0, timestampMs = 1000L),
+ TrackPoint(lat = 2.0, lon = 2.0, sogKnots = 2.0, cogDeg = 90.0, timestampMs = 2000L),
+ TrackPoint(lat = 3.0, lon = 3.0, sogKnots = 3.0, cogDeg = 180.0, timestampMs = 3000L),
+ )
+ val result = roundTrip(points)
+ assertEquals(3, result.size)
+ assertEquals(1.0, result[0].lat, 0.00001)
+ assertEquals(2.0, result[1].lat, 0.00001)
+ assertEquals(3.0, result[2].lat, 0.00001)
+ }
+
+ @Test
+ fun `round-trip preserves timestamp`() {
+ val ts = 1712345678000L
+ val pt = TrackPoint(lat = 0.0, lon = 0.0, sogKnots = 0.0, cogDeg = 0.0, timestampMs = ts)
+ val result = roundTrip(listOf(pt)).first()
+ assertEquals(ts, result.timestampMs)
+ }
+
+ @Test
+ fun `track name with special chars is escaped`() {
+ val gpx = GpxSerializer.serialize(emptyList(), "Track & \"Fun\" <test>")
+ assertTrue(gpx.contains("Track &amp; &quot;Fun&quot; &lt;test&gt;"))
+ }
+}
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/track/TrackSummaryTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/track/TrackSummaryTest.kt
new file mode 100644
index 0000000..2daaf45
--- /dev/null
+++ b/android-app/app/src/test/kotlin/org/terst/nav/track/TrackSummaryTest.kt
@@ -0,0 +1,56 @@
+package org.terst.nav.track
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class TrackSummaryTest {
+
+ private fun pt(lat: Double, lon: Double, sog: Double = 5.0, ts: Long = 0L) =
+ TrackPoint(lat = lat, lon = lon, sogKnots = sog, cogDeg = 0.0, timestampMs = ts)
+
+ @Test
+ fun `distance between two points one nm apart`() {
+ // 1 nautical mile north along the prime meridian ≈ 0.01667° latitude
+ val a = pt(0.0, 0.0, ts = 0L)
+ val b = pt(0.016667, 0.0, ts = 60_000L)
+ val s = summarise(listOf(a, b))
+ assertEquals(1.0, s.distanceNm, 0.01)
+ }
+
+ @Test
+ fun `duration is last minus first timestamp`() {
+ val points = listOf(pt(0.0, 0.0, ts = 1_000L), pt(0.0, 0.0, ts = 61_000L))
+ assertEquals(60_000L, summarise(points).durationMs)
+ }
+
+ @Test
+ fun `max sog picks highest value`() {
+ val points = listOf(pt(0.0, 0.0, sog = 4.0), pt(0.0, 0.0, sog = 9.2), pt(0.0, 0.0, sog = 6.0))
+ assertEquals(9.2, summarise(points).maxSogKt, 0.001)
+ }
+
+ @Test
+ fun `avg wind is null when no wind data`() {
+ val s = summarise(listOf(pt(0.0, 0.0)))
+ assertNull(s.avgWindKt)
+ }
+
+ @Test
+ fun `avg wind averages available readings`() {
+ val points = listOf(
+ TrackPoint(0.0, 0.0, 5.0, 0.0, windSpeedKnots = 10.0),
+ TrackPoint(0.0, 0.0, 5.0, 0.0, windSpeedKnots = 20.0),
+ )
+ assertEquals(15.0, summarise(points).avgWindKt!!, 0.001)
+ }
+
+ @Test
+ fun `avg wave height averages available readings`() {
+ val points = listOf(
+ TrackPoint(0.0, 0.0, 5.0, 0.0, waveHeightM = 1.0),
+ TrackPoint(0.0, 0.0, 5.0, 0.0, waveHeightM = 3.0),
+ )
+ assertEquals(2.0, summarise(points).avgWaveHeightM!!, 0.001)
+ }
+}
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/ui/DirectionArrowViewTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/ui/DirectionArrowViewTest.kt
new file mode 100644
index 0000000..e26b67d
--- /dev/null
+++ b/android-app/app/src/test/kotlin/org/terst/nav/ui/DirectionArrowViewTest.kt
@@ -0,0 +1,25 @@
+package org.terst.nav.ui
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class DirectionArrowViewTest {
+
+ @Test
+ fun `bearing is normalised — values over 360 wrap`() {
+ assertEquals(10f, normalizeBearing(370f), 0.001f)
+ }
+
+ @Test
+ fun `bearing is normalised — negative values wrap`() {
+ assertEquals(350f, normalizeBearing(-10f), 0.001f)
+ }
+
+ @Test
+ fun `bearing is normalised — exactly 360 becomes 0`() {
+ assertEquals(0f, normalizeBearing(360f), 0.001f)
+ }
+}
+
+// Top-level helper extracted from DirectionArrowView for testability
+fun normalizeBearing(deg: Float): Float = ((deg % 360f) + 360f) % 360f
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/ui/InstrumentHandlerTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/ui/InstrumentHandlerTest.kt
new file mode 100644
index 0000000..a749ba2
--- /dev/null
+++ b/android-app/app/src/test/kotlin/org/terst/nav/ui/InstrumentHandlerTest.kt
@@ -0,0 +1,39 @@
+package org.terst.nav.ui
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.util.Locale
+
+class InstrumentHandlerTest {
+
+ @Test
+ fun `metresToFeet converts correctly`() {
+ assertEquals(3.28f, metresToFeet(1.0).toFloat(), 0.01f)
+ }
+
+ @Test
+ fun `metresToFeet zero returns zero`() {
+ assertEquals(0f, metresToFeet(0.0).toFloat(), 0.001f)
+ }
+
+ @Test
+ fun `formatFt formats to one decimal`() {
+ val result = formatFt(3.28084, Locale.US)
+ assertEquals("3.3", result)
+ }
+
+ @Test
+ fun `formatBearing appends degree symbol`() {
+ assertEquals("275°", formatBearing(275.0, Locale.US))
+ }
+
+ @Test
+ fun `formatBearing rounds to zero decimals`() {
+ assertEquals("123°", formatBearing(123.4, Locale.US))
+ }
+
+ @Test
+ fun `formatPeriod appends s`() {
+ assertEquals("· 14s", formatPeriod(14.0, Locale.US))
+ }
+}