diff options
| author | Claude <noreply@anthropic.com> | 2026-05-26 08:07:50 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-05-26 08:07:50 +0000 |
| commit | f564e06338f846930300c366774efb97c042a5fb (patch) | |
| tree | 369ec57c831fb3b3de7220f4ba06fb3cb6d32440 /android-app/app/src/test/kotlin/org/terst | |
| parent | bbfcb667d9510e604676ac92cecc69f9bc39e440 (diff) | |
| parent | 3673ed0ef79d776c6da3bfa0db9de8b08326fd32 (diff) | |
Merge claude/whats-next-0XCO3: fix ANR, tack detection, empty-state flash
Diffstat (limited to 'android-app/app/src/test/kotlin/org/terst')
3 files changed, 191 insertions, 0 deletions
diff --git a/android-app/app/src/test/kotlin/org/terst/nav/logbook/LogbookRepositoryTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/logbook/LogbookRepositoryTest.kt new file mode 100644 index 0000000..1161065 --- /dev/null +++ b/android-app/app/src/test/kotlin/org/terst/nav/logbook/LogbookRepositoryTest.kt @@ -0,0 +1,69 @@ +package org.terst.nav.logbook + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class LogbookRepositoryTest { + + private lateinit var storage: LogbookStorage + private lateinit var repo: LogbookRepository + + @Before fun setUp() { + storage = mockk(relaxed = true) + every { storage.loadAll() } returns emptyList() + repo = LogbookRepository(storage) + } + + @Test fun `save assigns unique sequential ids`() { + val a = repo.save(entry("a")) + val b = repo.save(entry("b")) + assertNotEquals(a.id, b.id) + assertEquals(a.id + 1, b.id) + } + + @Test fun `getAll returns entries in insertion order`() { + repo.save(entry("first")) + repo.save(entry("second")) + assertEquals(listOf("first", "second"), repo.getAll().map { it.text }) + } + + @Test fun `save calls storage saveAll`() { + repo.save(entry("stored")) + verify { storage.saveAll(any()) } + } + + @Test fun `repository loads initial entries from storage on init`() { + val preloaded = entry("preloaded").copy(id = 99L) + every { storage.loadAll() } returns listOf(preloaded) + val repo2 = LogbookRepository(storage) + assertTrue(repo2.getAll().any { it.text == "preloaded" }) + } + + @Test fun `ids continue from max stored id after reload`() { + every { storage.loadAll() } returns listOf(entry("old").copy(id = 50L)) + val repo2 = LogbookRepository(storage) + val saved = repo2.save(entry("new")) + assertEquals(51L, saved.id) + } + + @Test fun `saved entry inherits text and location`() { + val e = entry("test note").copy(lat = 19.669, lon = -156.027) + val saved = repo.save(e) + assertEquals("test note", saved.text) + assertEquals(19.669, saved.lat!!, 0.0001) + assertEquals(-156.027, saved.lon!!, 0.0001) + } + + private fun entry(text: String) = LogEntry( + timestampMs = System.currentTimeMillis(), + text = text, + entryType = EntryType.GENERAL + ) +} diff --git a/android-app/app/src/test/kotlin/org/terst/nav/track/TackDetectorGpxTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/track/TackDetectorGpxTest.kt new file mode 100644 index 0000000..83ca259 --- /dev/null +++ b/android-app/app/src/test/kotlin/org/terst/nav/track/TackDetectorGpxTest.kt @@ -0,0 +1,25 @@ +package org.terst.nav.track + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class TackDetectorGpxTest { + + @Test + fun `detects 6 tacks in April 22 test file`() { + val stream = javaClass.classLoader!! + .getResourceAsStream("nav_20260422_190543.gpx") + ?: error("GPX test file not found in test resources") + val points = GpxParser.parse(stream) + assertTrue("Expected >100 points, got ${points.size}", points.size > 100) + + val tacks = TackDetector.detectTacks(points) + assertEquals( + "Expected 6 tacks in test track; got ${tacks.size}: ${ + tacks.map { "%.0f→%.0f".format(it.cogBefore, it.cogAfter) } + }", + 6, tacks.size + ) + } +} diff --git a/android-app/app/src/test/kotlin/org/terst/nav/tripreport/TripReportGeneratorTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/tripreport/TripReportGeneratorTest.kt new file mode 100644 index 0000000..8209d1a --- /dev/null +++ b/android-app/app/src/test/kotlin/org/terst/nav/tripreport/TripReportGeneratorTest.kt @@ -0,0 +1,97 @@ +package org.terst.nav.tripreport + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.terst.nav.logbook.EntryType +import org.terst.nav.logbook.LogEntry +import org.terst.nav.track.TrackPoint + +class TripReportGeneratorTest { + + private val gen = TripReportGenerator() + + private fun pt(lat: Double, lon: Double, sog: Double = 5.0, ts: Long) = + TrackPoint(lat = lat, lon = lon, sogKnots = sog, cogDeg = 0.0, timestampMs = ts) + + private fun entry(ts: Long, text: String = "note", photoPath: String? = null) = LogEntry( + timestampMs = ts, text = text, entryType = EntryType.GENERAL, photoPath = photoPath + ) + + // ── generateSummary ─────────────────────────────────────────────────────── + + @Test + fun `empty points returns zero summary`() { + val s = gen.generateSummary(emptyList(), emptyList()) + assertEquals(0.0, s.distanceNm, 0.0) + assertEquals(0.0, s.maxSogKts, 0.0) + assertTrue(s.logEntries.isEmpty()) + } + + @Test + fun `distance between two points one nm apart`() { + val points = listOf(pt(0.0, 0.0, ts = 0L), pt(0.016667, 0.0, ts = 60_000L)) + val s = gen.generateSummary(points, emptyList()) + assertEquals(1.0, s.distanceNm, 0.02) + } + + @Test + fun `max sog picks highest value`() { + val points = listOf(pt(0.0, 0.0, sog = 3.0, ts = 0L), pt(0.0, 0.0, sog = 8.5, ts = 1000L)) + val s = gen.generateSummary(points, emptyList()) + assertEquals(8.5, s.maxSogKts, 0.001) + } + + @Test + fun `log entries within track time range are included`() { + val points = listOf(pt(0.0, 0.0, ts = 1000L), pt(0.0, 0.0, ts = 5000L)) + val logs = listOf(entry(500L, "before"), entry(1000L, "start"), entry(3000L, "middle"), entry(5000L, "end"), entry(6000L, "after")) + val s = gen.generateSummary(points, logs) + assertEquals(listOf("start", "middle", "end"), s.logEntries.map { it.text }) + } + + @Test + fun `log entries outside track time range are excluded`() { + val points = listOf(pt(0.0, 0.0, ts = 10_000L), pt(0.0, 0.0, ts = 20_000L)) + val logs = listOf(entry(5_000L, "too early"), entry(25_000L, "too late")) + val s = gen.generateSummary(points, logs) + assertTrue(s.logEntries.isEmpty()) + } + + @Test + fun `start and end times match first and last point`() { + val points = listOf(pt(0.0, 0.0, ts = 1234L), pt(0.0, 0.0, ts = 9876L)) + val s = gen.generateSummary(points, emptyList()) + assertEquals(1234L, s.startTimeMs) + assertEquals(9876L, s.endTimeMs) + } + + // ── generateNarrative ───────────────────────────────────────────────────── + + @Test + fun `narrative contains passage header and distance`() { + val points = listOf(pt(0.0, 0.0, sog = 5.0, ts = 0L), pt(0.016667, 0.0, sog = 5.0, ts = 3_600_000L)) + val s = gen.generateSummary(points, emptyList()) + val narrative = gen.generateNarrative(s) + assertTrue("Expected PASSAGE header", narrative.contains("PASSAGE")) + assertTrue("Expected distance in nm", narrative.contains("nm")) + assertTrue("Expected avg speed", narrative.contains("avg")) + assertTrue("Expected max speed", narrative.contains("max")) + } + + @Test + fun `narrative includes log entry text`() { + val points = listOf(pt(0.0, 0.0, ts = 0L), pt(0.016667, 0.0, ts = 3_600_000L)) + val s = gen.generateSummary(points, listOf(entry(1_000L, "hoisted main"))) + val narrative = gen.generateNarrative(s) + assertTrue("Expected log entry in narrative", narrative.contains("hoisted main")) + } + + @Test + fun `narrative marks photo entries`() { + val points = listOf(pt(0.0, 0.0, ts = 0L), pt(0.016667, 0.0, ts = 3_600_000L)) + val s = gen.generateSummary(points, listOf(entry(1_000L, "crew photo", photoPath = "/some/path.jpg"))) + val narrative = gen.generateNarrative(s) + assertTrue("Expected [photo] marker", narrative.contains("[photo]")) + } +} |
