diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt index e39aa76..d9d8ffc 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt @@ -260,4 +260,52 @@ class ArchitectureRulesTest { assertThat(offenders).isEmpty() } + + /** + * M8 D24: ICU is a seam, not an ambient API. Every localised zone name the + * app shows comes through `data/zones/`, so an OEM build with a broken ICU + * degrades one caption instead of crashing a tab. + */ + @Test + fun `ICU is named only under the zone-names seam`() { + val offenders = kotlinSources.usingInCode("android.icu").filterNot { + it.contains("de/jeanlucmakiola/clockula/data/zones/") + } + + assertThat(offenders).isEmpty() + } + + /** + * M8 D24: `docs/PLAN.md` §8's "not sprinkled everywhere", made mechanical. + * The shape morph is the world clock's one showpiece; the ring screen and + * timer progress are sanctioned too, so widening this allowlist is a + * deliberate edit rather than a drift. + */ + @Test + fun `MaterialShapes is drawn only on the world clock face`() { + val offenders = kotlinSources + .usingInCode("MaterialShapes", "androidx.graphics.shapes") + .filterNot { it.contains("de/jeanlucmakiola/clockula/ui/worldclock/") } + + assertThat(offenders).isEmpty() + } + + /** + * M8 D24: the zone is a parameter, from `ZoneProvider` — never an ambient + * read at a call site. Scoped to `domain/` and `ui/` on purpose: + * `alarm/ring/RingNotifications.kt` formats a snooze time with + * `systemDefault()` today, and rewriting it is not M8's business. + */ + @Test + fun `the domain and the ui read no ambient time zone`() { + val domainAndUi = kotlinSources.filter { + val path = it.invariantSeparatorsPath + path.contains("de/jeanlucmakiola/clockula/domain/") || + path.contains("de/jeanlucmakiola/clockula/ui/") + } + + val offenders = domainAndUi.usingInCode("ZoneId.systemDefault()", "TimeZone.getDefault()") + + assertThat(offenders).isEmpty() + } } diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/testing/FakeAlarmSeams.kt b/app/src/test/java/de/jeanlucmakiola/clockula/testing/FakeAlarmSeams.kt index fdcc1db..76708b2 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/testing/FakeAlarmSeams.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/testing/FakeAlarmSeams.kt @@ -138,8 +138,13 @@ class FakeAlarmCapabilities( } /** The device's zone, which a test can change under a scheduled alarm. */ -class FakeZoneProvider(var zone: ZoneId = BERLIN) : ZoneProvider { +class FakeZoneProvider( + var zone: ZoneId = BERLIN, + var availableIds: Set = ZoneId.getAvailableZoneIds(), +) : ZoneProvider { override fun current(): ZoneId = zone + + override fun available(): Set = availableIds } class FakeBootIdProvider(