From 83995c39db4906be98113f8d1c44148a7fb81d41 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 22 Sep 2026 10:24:08 +0200 Subject: [PATCH] test(arch): the stopwatch stays silent, and tabular figures stay in the theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new rules and two extended. The stopwatch may not name a player, a vibrator, an audio manager — or a wake lock: it is the one timekeeper in the app that never makes a sound and never holds the CPU awake, and that is now a build failure rather than a promise in a comment. `fontFeatureSettings` may appear only under `ui/theme/`, so the figure settings stay in one place instead of spreading to call sites. The Android-free list gains the engine; the screen's needle list gains the service and its notifications. --- .../clockula/ArchitectureRulesTest.kt | 49 ++++++++++++++++++- 1 file changed, 48 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 821acd8..e39aa76 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt @@ -136,6 +136,7 @@ class ArchitectureRulesTest { path.contains("de/jeanlucmakiola/clockula/domain/alarm/") || path.endsWith("de/jeanlucmakiola/clockula/alarm/AlarmEngine.kt") || path.endsWith("de/jeanlucmakiola/clockula/timer/TimerEngine.kt") || + path.endsWith("de/jeanlucmakiola/clockula/stopwatch/StopwatchEngine.kt") || path.endsWith("de/jeanlucmakiola/clockula/alarm/AlarmResolver.kt") || path.contains("de/jeanlucmakiola/clockula/system/") } @@ -194,7 +195,15 @@ class ArchitectureRulesTest { val ui = kotlinSources .filter { it.invariantSeparatorsPath.contains("de/jeanlucmakiola/clockula/ui/") } - val offenders = ui.usingInCode("TimerScheduler", "TimerService", "TimerRingPolicy", "AlarmManager") + val offenders = ui.usingInCode( + "TimerScheduler", + "TimerService", + "TimerRingPolicy", + "AlarmManager", + // M7 D23: the screen posts no notification and starts no service. + "StopwatchService", + "StopwatchNotifications", + ) assertThat(offenders).isEmpty() } @@ -213,4 +222,42 @@ class ArchitectureRulesTest { assertThat(offenders).isEmpty() } + + /** + * M7 D19: the stopwatch is silent. It never opens audio, never vibrates and + * never takes a wake lock — which is the one thing a future "make the lap + * button click" change would quietly break. + */ + @Test + fun `the stopwatch makes no sound`() { + val stopwatch = kotlinSources + .filter { it.invariantSeparatorsPath.contains("de/jeanlucmakiola/clockula/stopwatch/") } + + val offenders = stopwatch.usingInCode( + "MediaPlayer", + "RingAudioPlayer", + "RingVibrator", + "Vibrator", + "AudioManager", + // D3's "no wake lock, ever" is half of the promise this rule makes, + // so the needle list has to name it too. + "PowerManager", + "WakeLock", + ) + + assertThat(offenders).isEmpty() + } + + /** + * M7 D12: tabular figures are configured **once**, in the theme, so no call + * site ever re-specifies them and no readout can be left proportional. + */ + @Test + fun `tabular figures are configured only in the theme`() { + val offenders = kotlinSources.containing("fontFeatureSettings").filterNot { + it.contains("de/jeanlucmakiola/clockula/ui/theme/") + } + + assertThat(offenders).isEmpty() + } }