From ff5854b75f2ba7c77d467fd9523e2a23060a7c46 Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Sun, 15 Mar 2026 14:20:21 +0000 Subject: feat: integrate AIS into ViewModel and MapFragment with vessel symbol layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainViewModel: add _aisTargets StateFlow, processAisSentence(), refreshAisFromInternet() - AisRepository: add ingestVessel() for internet-sourced vessels - MapFragment: add AIS vessel SymbolLayer with heading-based rotation and zoom-gated labels - MainActivity: add startAisHardwareFeed() TCP stub, wire viewModel - ic_ship_arrow.xml: new vector drawable for AIS target icons - MainViewModelTest: 3 new AIS tests (processAisSentence happy path, dedup, non-AIS sentence) - JVM test harness: /tmp/ais-vm-test-runner/ — 3 tests GREEN Co-Authored-By: Claude Sonnet 4.6 --- .../kotlin/org/terst/nav/ui/MainViewModelTest.kt | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'android-app/app/src/test') diff --git a/android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelTest.kt b/android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelTest.kt index edecdd5..0f5cefe 100644 --- a/android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelTest.kt +++ b/android-app/app/src/test/kotlin/org/terst/nav/ui/MainViewModelTest.kt @@ -1,6 +1,7 @@ package org.terst.nav.ui import app.cash.turbine.test +import org.terst.nav.ais.AisVessel import org.terst.nav.data.model.ForecastItem import org.terst.nav.data.model.WindArrow import org.terst.nav.data.repository.WeatherRepository @@ -102,4 +103,43 @@ class MainViewModelTest { cancelAndIgnoreRemainingEvents() } } + + // ── AIS integration tests ──────────────────────────────────────────────── + + @Test + fun `processAisSentence valid type-1 NMEA adds 1 vessel to aisTargets`() { + coEvery { repo.fetchWindArrow(any(), any()) } returns Result.success(sampleArrow) + coEvery { repo.fetchForecastItems(any(), any()) } returns Result.success(sampleForecast) + vm = makeVm() + + // Known real type-1 sentence; MMSI = 227006760 + vm.processAisSentence("!AIVDM,1,1,,A,13HOI:0P0000vocH;`5HF>0<0000,0*54") + + assertEquals(1, vm.aisTargets.value.size) + assertEquals(227006760, vm.aisTargets.value[0].mmsi) + } + + @Test + fun `processAisSentence same MMSI twice keeps exactly 1 vessel in aisTargets`() { + coEvery { repo.fetchWindArrow(any(), any()) } returns Result.success(sampleArrow) + coEvery { repo.fetchForecastItems(any(), any()) } returns Result.success(sampleForecast) + vm = makeVm() + + val sentence = "!AIVDM,1,1,,A,13HOI:0P0000vocH;`5HF>0<0000,0*54" + vm.processAisSentence(sentence) + vm.processAisSentence(sentence) + + assertEquals(1, vm.aisTargets.value.size) + } + + @Test + fun `processAisSentence non-AIS sentence leaves aisTargets empty`() { + coEvery { repo.fetchWindArrow(any(), any()) } returns Result.success(sampleArrow) + coEvery { repo.fetchForecastItems(any(), any()) } returns Result.success(sampleForecast) + vm = makeVm() + + vm.processAisSentence("\$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A") + + assertEquals(0, vm.aisTargets.value.size) + } } -- cgit v1.2.3