Files
LibreMail/gradle/libs.versions.toml
T
JMR-devandClaude Opus 4.8 324f7c2c51 fix(test): resolve Robolectric android-all via Gradle so the JVM Compose test runs in CI (#373)
Robolectric resolved its android-all-instrumented runtime jar lazily at test
time via its own MavenDependencyResolver/MavenArtifactFetcher, and that
download is unreliable on CI runners: AddAnotherAccountScreenJvmTest failed
with `AssertionError at MavenArtifactFetcher ... IOException` ("Failed to
fetch maven artifact"), though it passed locally where ~/.m2 was warm.

Resolve the jar through Gradle instead (reliable, cached, persisted by the CI
Gradle cache) and hand it to Robolectric in offline mode so it never hits the
network at test time:
- Pin org.robolectric:android-all-instrumented:16-robolectric-13921718-i7
  (exactly what Robolectric 4.16.1 DefaultSdkProvider maps @Config(sdk=36) to)
  in the version catalog.
- Add it to a dedicated resolvable configuration (NOT testImplementation/
  testRuntimeOnly, which would flatten the ~200MB instrumented framework onto
  the JVM test classpath and collide with the stub android.jar).
- syncRobolectricAndroidAll stages the jar under its Maven filename, and
  robolectric.offline + robolectric.dependency.dir point Robolectric's
  LocalDependencyResolver at it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 15:49:07 -05:00

152 lines
9.9 KiB
TOML

[versions]
agp = "9.2.0"
kotlin = "2.4.0"
ksp = "2.3.9"
hilt = "2.60"
hiltNavigationCompose = "1.3.0"
coreKtx = "1.17.0"
lifecycle = "2.9.4"
activityCompose = "1.12.4"
navigationCompose = "2.9.8"
webkit = "1.12.1"
biometric = "1.1.0"
composeBom = "2026.06.00"
room = "2.8.4"
paging = "3.3.6"
sqlcipher = "4.16.0"
datastore = "1.2.1"
work = "2.11.2"
coroutines = "1.10.2"
# Forced above androidx.savedstate's transitive 1.7.3 (shared with the androidTest classpath via
# AGP 9 consistent resolution); Room's schema-bundle serializers need >= 1.8.0 or MigrationTestHelper
# throws AbstractMethodError on GeneratedSerializer.typeParametersSerializers().
kotlinxSerialization = "1.8.1"
appauth = "0.11.1"
angusMail = "2.0.5"
junit = "4.13.2"
androidxJunit = "1.2.1"
espresso = "3.7.0"
androidxTestRules = "1.7.0"
turbine = "1.2.1"
mockk = "1.14.11"
greenmail = "2.1.9"
# Robolectric — runs the Android framework on the JVM so Compose UI (createComposeRule) can be
# unit-tested without an emulator (issue #373). 4.16.x is the first line to support compileSdk 36
# (Baklava) and requires JDK 21 for it; API 37 (preview) is unsupported, so Robolectric tests pin
# @Config(sdk = 36) — also set in app/src/test/resources/robolectric.properties.
robolectric = "4.16.1"
# The instrumented android-all runtime jar Robolectric loads into its sandbox to run the Android
# framework on the JVM. By DEFAULT Robolectric downloads this ~200 MB artifact from Maven Central
# lazily at TEST time (MavenDependencyResolver -> MavenArtifactFetcher); that runtime fetch is
# unreliable on CI runners and failed the JVM Compose PoC with "Failed to fetch maven artifact"
# (issue #373). Pinning it lets Gradle resolve+cache it and hand it to Robolectric in offline mode
# (see the robolectricAndroidAll wiring in app/build.gradle.kts) so no network fetch happens at
# test time. The value is BOTH Robolectric-version- and SDK-specific: it is exactly what
# Robolectric 4.16.1's DefaultSdkProvider maps @Config(sdk = 36) to — androidVersion "16", build
# "13921718", preinstrumented suffix "-i7". Bump it together with `robolectric` above, and update
# it if the pinned @Config(sdk = ..) / robolectric.properties SDK ever changes.
robolectricAndroidAll = "16-robolectric-13921718-i7"
errorprone = "2.50.0"
# Lint/format. ktlint owns formatting; detekt owns static analysis (formatting ruleset off).
# detekt 2.0 is the only line with Gradle 9 support (stable 1.23.x tops out at Gradle 8.12).
ktlint = "14.2.0"
detekt = "2.0.0-alpha.5"
# JaCoCo coverage agent/report tool (issue #192). Pinned above Gradle's bundled default so the
# coverage agent understands Kotlin 2.4.0 bytecode on JDK 21.
jacoco = "0.8.13"
[libraries]
# AndroidX core / lifecycle / activity / navigation
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }
# Biometric / device-credential auth for the opt-in app-lock + encrypted-cache unlock.
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "biometric" }
# Compose (versions managed by the BOM)
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
# Hilt (DI)
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
# Pulled onto the compile classpath because Hilt/Dagger generated code references it.
error-prone-annotations = { group = "com.google.errorprone", name = "error_prone_annotations", version.ref = "errorprone" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "hiltNavigationCompose" }
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "hiltNavigationCompose" }
# Room (local cache)
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
# Room MigrationTestHelper (instrumented migration tests).
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
# Room PagingSource support — lets @Query methods return androidx.paging.PagingSource.
androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" }
# SQLCipher — opt-in at-rest encryption of the Room cache.
sqlcipher-android = { group = "net.zetetic", name = "sqlcipher-android", version.ref = "sqlcipher" }
# Paging 3 — pages the unified "All inboxes" list so its query/recomposition cost scales with the
# visible window, not the whole cache (issue #124). runtime = Pager/PagingData; compose =
# collectAsLazyPagingItems; testing = asSnapshot for JVM unit tests.
androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "paging" }
androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
androidx-paging-testing = { group = "androidx.paging", name = "paging-testing", version.ref = "paging" }
# DataStore (settings) / WorkManager (sync) — wired in later increments
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
# TestListenableWorkerBuilder for instrumented CoroutineWorker tests (issue #226).
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "work" }
# Coroutines
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
# Serialization BOM — imported as a platform (not used directly) to raise the transitive
# kotlinx-serialization runtime to the version Room's schema bundles were compiled against.
kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-bom", version.ref = "kotlinxSerialization" }
# Email transport / OAuth — wired in later increments
appauth = { group = "net.openid", name = "appauth", version.ref = "appauth" }
angus-mail = { group = "org.eclipse.angus", name = "angus-mail", version.ref = "angusMail" }
# Test
junit = { group = "junit", name = "junit", version.ref = "junit" }
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlin" }
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
# mockk's Android artifact for instrumented tests (dexmaker/byte-buddy-android backed).
mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
greenmail = { group = "com.icegreen", name = "greenmail", version.ref = "greenmail" }
# Robolectric — JVM Android framework for unit-testing Compose UI without an emulator (issue #373).
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
# Resolved via Gradle (not Robolectric's runtime Maven fetch) and staged for offline use so the JVM
# Compose tests run in CI without a flaky test-time download — see app/build.gradle.kts (issue #373).
robolectric-android-all-instrumented = { group = "org.robolectric", name = "android-all-instrumented", version.ref = "robolectricAndroidAll" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
androidx-espresso-intents = { group = "androidx.test.espresso", name = "espresso-intents", version.ref = "espresso" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
detekt = { id = "dev.detekt", version.ref = "detekt" }