plugins { id("com.android.application") id("org.jetbrains.kotlin.android") 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 { namespace = "org.terst.feedreader" compileSdk = 34 defaultConfig { applicationId = "org.terst.feedreader" minSdk = 26 targetSdk = 34 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { debug { enableUnitTestCoverage = true } release { isMinifyEnabled = false } } buildFeatures { compose = true } kotlinOptions { jvmTarget = "11" } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } } dependencies { val composeBom = platform("androidx.compose:compose-bom:2024.02.00") implementation(composeBom) implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.activity:activity-compose:1.9.0") implementation("androidx.compose.material3:material3") implementation("androidx.compose.material:material-icons-extended") implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.navigation:navigation-compose:2.7.7") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.1") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.1") implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") ksp("androidx.room:room-compiler:2.6.1") implementation("androidx.work:work-runtime-ktx:2.9.0") implementation("androidx.datastore:datastore-preferences:1.2.1") implementation("androidx.security:security-crypto:1.1.0") implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") implementation("io.coil-kt:coil-compose:2.6.0") testImplementation("junit:junit:4.13.2") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") testImplementation("androidx.room:room-testing:2.6.1") // Instrumented tests: ReaderExtractor needs a real WebView to run Readability.js, so // extraction-pipeline regression tests can't be plain JVM unit tests. MockWebServer serves // fixture HTML over real http:// -- ReaderExtractor's WebView has allowFileAccess = false // (deliberately, see its own comments), so file:// fixtures wouldn't load. androidTestImplementation("androidx.test.ext:junit:1.3.0") androidTestImplementation("androidx.test:runner:1.7.0") androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0") androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") } detekt { buildUponDefaultConfig = true allRules = false config.setFrom(files("$rootDir/config/detekt/detekt.yml")) baseline = file("$rootDir/config/detekt/baseline.xml") } tasks.withType().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("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") }) }