diff --git a/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt b/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt index 69309d3..ae62d7b 100644 --- a/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/clockula/ArchitectureRulesTest.kt @@ -70,6 +70,53 @@ class ArchitectureRulesTest { assertThat(offenders).isEmpty() } + /** + * Compose may live in `ui/` and in the one activity that hosts it, and + * nowhere else — the shell's decision surface is pure Kotlin by + * construction, which is what earns the no-Robolectric posture (D17). + */ + private val composeAllowlist: List = kotlinSources + .filter { file -> file.readLines().any { it.startsWith("import androidx.compose.") } } + .map { it.invariantSeparatorsPath } + + @Test + fun `Compose stays inside the ui package and its one host activity`() { + val offenders = composeAllowlist.filterNot { + it.contains("de/jeanlucmakiola/clockula/ui/") || + it.endsWith("de/jeanlucmakiola/clockula/MainActivity.kt") + } + + assertThat(offenders).isEmpty() + } + + @Test + fun `the ui layer names no literal colour`() { + val literals = listOf( + "Color(0x", + "Color.Red", "Color.Green", "Color.Blue", "Color.Yellow", "Color.Cyan", + "Color.Magenta", "Color.White", "Color.Black", "Color.Gray", + "Color.LightGray", "Color.DarkGray", + ) + val ui = kotlinSources.filter { file -> + val path = file.invariantSeparatorsPath + path.contains("de/jeanlucmakiola/clockula/ui/") && + !path.contains("de/jeanlucmakiola/clockula/ui/theme/") + } + + val offenders = ui.containing(*literals.toTypedArray()) + + assertThat(offenders).isEmpty() + } + + @Test + fun `exactly one file outside the ui package may import Compose`() { + val outsideUi = composeAllowlist.filterNot { + it.contains("de/jeanlucmakiola/clockula/ui/") + } + + assertThat(outsideUi).hasSize(1) + } + @Test fun `the alarm engine and its pure neighbours stay free of Android types`() { val jvmTestable = kotlinSources.filter { file ->