diff options
Diffstat (limited to 'android-app/app/src/test/kotlin/com/example/androidapp')
| -rw-r--r-- | android-app/app/src/test/kotlin/com/example/androidapp/logbook/LogbookFormatterTest.kt | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/android-app/app/src/test/kotlin/com/example/androidapp/logbook/LogbookFormatterTest.kt b/android-app/app/src/test/kotlin/com/example/androidapp/logbook/LogbookFormatterTest.kt new file mode 100644 index 0000000..30b421f --- /dev/null +++ b/android-app/app/src/test/kotlin/com/example/androidapp/logbook/LogbookFormatterTest.kt @@ -0,0 +1,178 @@ +package com.example.androidapp.logbook + +import com.example.androidapp.data.model.LogbookEntry +import org.junit.Assert.* +import org.junit.Test + +class LogbookFormatterTest { + + // 2021-06-15 08:00:00 UTC = 1623744000000 ms + private val t0 = 1_623_744_000_000L + + private fun entry( + ts: Long = t0, + lat: Double = 41.39, + lon: Double = -71.202, + sog: Double = 6.2, + cog: Double = 225.0, + windKt: Double? = 15.0, + windDir: Double? = 225.0, + baro: Double? = 1018.0, + depth: Double? = 14.0, + event: String? = "Departed slip", + notes: String? = null + ) = LogbookEntry(ts, lat, lon, sog, cog, windKt, windDir, baro, depth, event, notes) + + // --- formatTime --- + + @Test + fun `formatTime returns HH_MM for UTC midnight`() { + // 2021-06-15 00:00:00 UTC + val ts = 1_623_715_200_000L + assertEquals("00:00", LogbookFormatter.formatTime(ts)) + } + + @Test + fun `formatTime returns correct UTC hour for known timestamp`() { + // t0 = 2021-06-15 08:00:00 UTC + assertEquals("08:00", LogbookFormatter.formatTime(t0)) + } + + @Test + fun `formatTime pads single-digit hour and minute`() { + // 2021-06-15 01:05:00 UTC = 1623715200000 + 65*60*1000 = 1623715200000 + 3900000 + val ts = 1_623_715_200_000L + 65 * 60_000L + assertEquals("01:05", LogbookFormatter.formatTime(ts)) + } + + // --- formatPosition --- + + @Test + fun `formatPosition north east`() { + // 41.39°N → 41°23.4N, 71.202°E → 71°12.1E + val result = LogbookFormatter.formatPosition(41.39, 71.202) + assertEquals("41°23.4N 71°12.1E", result) + } + + @Test + fun `formatPosition south west`() { + // -41.39°S → 41°23.4S, -71.202°W → 71°12.1W + val result = LogbookFormatter.formatPosition(-41.39, -71.202) + assertEquals("41°23.4S 71°12.1W", result) + } + + @Test + fun `formatPosition zero zero`() { + val result = LogbookFormatter.formatPosition(0.0, 0.0) + assertEquals("0°0.0N 0°0.0E", result) + } + + // --- formatWind --- + + @Test + fun `formatWind null knots returns empty string`() { + assertEquals("", LogbookFormatter.formatWind(null, null)) + } + + @Test + fun `formatWind with knots and null direction returns knots only`() { + assertEquals("15kt", LogbookFormatter.formatWind(15.0, null)) + } + + @Test + fun `formatWind 225 degrees is SW`() { + assertEquals("15kt SW", LogbookFormatter.formatWind(15.0, 225.0)) + } + + @Test + fun `formatWind 0 degrees is N`() { + assertEquals("10kt N", LogbookFormatter.formatWind(10.0, 0.0)) + } + + @Test + fun `formatWind 360 degrees is N`() { + assertEquals("10kt N", LogbookFormatter.formatWind(10.0, 360.0)) + } + + @Test + fun `formatWind 90 degrees is E`() { + assertEquals("8kt E", LogbookFormatter.formatWind(8.0, 90.0)) + } + + // --- toCompassPoint --- + + @Test + fun `toCompassPoint covers all 16 cardinal and intercardinal points`() { + val expected = listOf("N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", + "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW") + expected.forEachIndexed { i, dir -> + val degrees = i * 22.5 + assertEquals("degrees=$degrees", dir, LogbookFormatter.toCompassPoint(degrees)) + } + } + + // --- toRow --- + + @Test + fun `toRow formats all fields correctly`() { + val row = LogbookFormatter.toRow(entry()) + assertEquals("08:00", row.time) + assertEquals("41°23.4N 71°12.1W", row.position) + assertEquals("6.2", row.sog) + assertEquals("225", row.cog) + assertEquals("15kt SW", row.wind) + assertEquals("1018", row.baro) + assertEquals("14m", row.depth) + assertEquals("Departed slip", row.eventNotes) + } + + @Test + fun `toRow combines event and notes with colon`() { + val row = LogbookFormatter.toRow(entry(event = "Reef #1", notes = "Strong gusts")) + assertEquals("Reef #1: Strong gusts", row.eventNotes) + } + + @Test + fun `toRow with only notes has no colon prefix`() { + val row = LogbookFormatter.toRow(entry(event = null, notes = "Calm seas")) + assertEquals("Calm seas", row.eventNotes) + } + + @Test + fun `toRow with null optional fields uses empty strings`() { + val e = LogbookEntry(t0, 0.0, 0.0, 0.0, 0.0) + val row = LogbookFormatter.toRow(e) + assertEquals("", row.wind) + assertEquals("", row.baro) + assertEquals("", row.depth) + assertEquals("", row.eventNotes) + } + + // --- toPage --- + + @Test + fun `toPage returns page with default title and correct column count`() { + val page = LogbookFormatter.toPage(emptyList()) + assertEquals("Trip Logbook", page.title) + assertEquals(8, page.columns.size) + } + + @Test + fun `toPage maps entries to rows in order`() { + val entries = listOf( + entry(ts = t0, event = "First"), + entry(ts = t0 + 3_600_000L, event = "Second") + ) + val page = LogbookFormatter.toPage(entries, "Voyage Log") + assertEquals("Voyage Log", page.title) + assertEquals(2, page.rows.size) + assertEquals("First", page.rows[0].eventNotes) + assertEquals("Second", page.rows[1].eventNotes) + } + + @Test + fun `toPage empty entries produces empty rows`() { + val page = LogbookFormatter.toPage(emptyList()) + assertTrue(page.rows.isEmpty()) + } +} |
