From 815a14235bece6aa85c09e6174d55fc7f2479b1e Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 22 Sep 2026 13:05:18 +0200 Subject: [PATCH] test(arch): ICU, MaterialShapes and the ambient zone each stay where they belong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new rules. `android.icu` may be named only under `data/zones/`, so the platform's name database stays behind its seam. `MaterialShapes` and `androidx.graphics.shapes` may be named only under `ui/worldclock/`, so the showpiece stays the one place that morphs. And nothing under `domain/` or `ui/` may read `ZoneId.systemDefault()` or `TimeZone.getDefault()` — the device's zone is an argument, or the tests cannot pin a single one. All three hold retroactively for M0 through M7. --- .../clockula/ArchitectureRulesTest.kt | 48 +++++++++++++++++++ .../clockula/testing/FakeAlarmSeams.kt | 7 ++- 2 files changed, 54 insertions(+), 1 deletion(-) 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(