summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-24 07:54:01 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-24 07:54:01 +0000
commitda942624a4abd82797f563cf8c57f1124c5de435 (patch)
treef7e4325a4783a77544e6ed1f0ac44ea844757fe8 /app
parentb299d57d6cc240381aed51f91e5a26f1e633d78d (diff)
Add TDD quality gate: detekt, coverage tracking, pre-commit hook; fix theme crash risk
CLAUDE.md now documents mandatory red-green-refactor TDD, a per-commit quality gate (build, tests, lint, detekt, coverage-non-decrease, WebView-JS justification, manifest permission diff, secret scan), and why each check exists (tracing back to real bugs this audit found). Wires up detekt (static analysis) and a standalone Jacoco setup (scoped to data/readability/opml/work/stash -- ui/ Compose screens are impractical to unit-test for coverage) via app/build.gradle.kts. Bumped Kotlin/KSP to 2.0.21/2.0.21-1.0.28 to match detekt 1.23.8's compatibility requirement. Also fixes the themes.xml bug the lint gate immediately caught: Theme.FeedReader's parent required API 29 while minSdk is 26 -- a real crash risk on Android 8.0-9.0 devices. Swapped to the plain (non-DayNight) DeviceDefault parent, since Compose already owns all real theming via a hardcoded darkColorScheme() regardless of system day/night state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017GMEkHeqKz6FLkmizowBTK
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle.kts55
-rw-r--r--app/src/main/res/values/themes.xml6
2 files changed, 60 insertions, 1 deletions
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 32313ab..03b240e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,6 +4,8 @@ plugins {
id("org.jetbrains.kotlin.plugin.serialization")
id("org.jetbrains.kotlin.plugin.compose")
id("com.google.devtools.ksp")
+ id("io.gitlab.arturbosch.detekt")
+ id("jacoco")
}
android {
@@ -19,6 +21,9 @@ android {
}
buildTypes {
+ debug {
+ enableUnitTestCoverage = true
+ }
release {
isMinifyEnabled = false
}
@@ -70,3 +75,53 @@ dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
testImplementation("androidx.room:room-testing:2.6.1")
}
+
+detekt {
+ buildUponDefaultConfig = true
+ allRules = false
+ config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
+ baseline = file("$rootDir/config/detekt/baseline.xml")
+}
+
+tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
+ reports {
+ xml.required.set(true)
+ html.required.set(true)
+ txt.required.set(false)
+ sarif.required.set(false)
+ }
+}
+
+// JVM unit-test line coverage over testable business logic. Compose screens under ui/ are
+// excluded -- they're impractical to unit-test for coverage and would make the metric
+// meaningless (see CLAUDE.md). Room- and KSP-generated code is excluded too, since coverage
+// there measures generated boilerplate, not anything we wrote.
+tasks.register<JacocoReport>("jacocoTestReport") {
+ dependsOn("testDebugUnitTest")
+ group = "verification"
+ description = "Generates a Jacoco coverage report for the debug unit tests."
+
+ reports {
+ xml.required.set(true)
+ html.required.set(true)
+ }
+
+ val fileFilter = listOf(
+ "**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*",
+ "android/**/*.*",
+ "**/ui/**",
+ "**/*_Impl.class", "**/*_Impl$*.class",
+ "**/di/**"
+ )
+ val kotlinDebugTree = fileTree("${layout.buildDirectory.get()}/tmp/kotlin-classes/debug") {
+ exclude(fileFilter)
+ }
+ val javaDebugTree = fileTree("${layout.buildDirectory.get()}/intermediates/javac/debug/classes") {
+ exclude(fileFilter)
+ }
+ classDirectories.setFrom(files(kotlinDebugTree, javaDebugTree))
+ sourceDirectories.setFrom(files("${project.projectDir}/src/main/java"))
+ executionData.setFrom(fileTree(layout.buildDirectory.get()) {
+ include("outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec", "jacoco/testDebugUnitTest.exec")
+ })
+}
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 9c5f0ff..b1eb502 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="Theme.FeedReader" parent="android:Theme.DeviceDefault.DayNight">
+ <!-- DayNight requires API 29 (minSdk here is 26) and buys nothing anyway: Compose owns all
+ real theming via darkColorScheme() regardless of system day/night state (see
+ MainActivity.kt), so this base window theme only ever needs to supply window chrome
+ (no title/action bar), not day/night-aware colors. -->
+ <style name="Theme.FeedReader" parent="android:Theme.DeviceDefault">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>