Lands the infrastructure + a runnable PoC for Robolectric-backed JVM unit testing of Jetpack Compose UI (umbrella #373). Robolectric runs the Android framework on the JVM, so createComposeRule() can drive composables with no emulator — bringing render-only screens into the JaCoCo JVM-testable + counted surface, so the jacocoNonJvmTestableSurface exclusions can shrink.
AddAnotherAccountScreenJvmTest — 3 tests, green on :app:testDebugUnitTest, no emulator. First test ~5s (Robolectric + NATIVE-graphics warmup), the rest <0.1s each.
Uses the v2 API androidx.compose.ui.test.junit4.v2.createComposeRule (v1 is deprecated in this BOM).
Changes
Robolectric 4.16.1 in test scope + Compose ui-test-junit4 / ui-test-manifest (reusing the BOM the app already applies).
testOptions.unitTests.isIncludeAndroidResources = true so stringResource(...) + Material3 theming resolve on the JVM.
app/src/test/resources/robolectric.properties pins sdk=36 — targetSdk 37 is a preview level Robolectric 4.16 ships no sandbox for (API 37 unsupported).
PoC test AddAnotherAccountScreenJvmTest (@RunWith(RobolectricTestRunner) + @GraphicsMode(NATIVE) + @Config(sdk = [36])).
Removed **/AddAnotherAccountScreen* from jacocoNonJvmTestableSurface (now genuinely JVM-covered); the bucket comment now documents the shrink-one-screen-at-a-time mechanism.
Not in this PR (deferred)
The remaining ~26 excluded Compose UI files are converted in per-area tickets under #373 (format controls, onboarding/lock, account setup, mail lists, settings, reader, compose editor, mailbox, app shell).
The JaCoCo floor re-ratchet is the final step of #373, after all conversions are stable — deliberately NOT touched here (floor stays 0.79).
Existing instrumented androidTest Compose tests are kept as-is (the instrumented E2E stays).
Gate
assembleDebug + testDebugUnitTest + jacocoTestCoverageVerification + compileDebugAndroidTestKotlin + lintDebug + ktlintCheck + detekt — all green locally.
## What
Lands the **infrastructure + a runnable PoC** for Robolectric-backed JVM unit testing of Jetpack Compose UI (umbrella #373). Robolectric runs the Android framework on the JVM, so `createComposeRule()` can drive composables with no emulator — bringing render-only screens into the JaCoCo JVM-testable + counted surface, so the `jacocoNonJvmTestableSurface` exclusions can shrink.
## Feasibility: proven on this exact stack
Verified on AGP 9.2 (built-in Kotlin), Kotlin 2.4.0, Compose BOM 2026.06.00, JDK 21, JUnit4:
- `AddAnotherAccountScreenJvmTest` — 3 tests, **green on `:app:testDebugUnitTest`**, no emulator. First test ~5s (Robolectric + NATIVE-graphics warmup), the rest <0.1s each.
- Uses the **v2** API `androidx.compose.ui.test.junit4.v2.createComposeRule` (v1 is deprecated in this BOM).
## Changes
- **Robolectric 4.16.1** in `test` scope + Compose `ui-test-junit4` / `ui-test-manifest` (reusing the BOM the app already applies).
- `testOptions.unitTests.isIncludeAndroidResources = true` so `stringResource(...)` + Material3 theming resolve on the JVM.
- `app/src/test/resources/robolectric.properties` pins `sdk=36` — targetSdk 37 is a preview level Robolectric 4.16 ships no sandbox for (API 37 unsupported).
- PoC test `AddAnotherAccountScreenJvmTest` (`@RunWith(RobolectricTestRunner)` + `@GraphicsMode(NATIVE)` + `@Config(sdk = [36])`).
- Removed `**/AddAnotherAccountScreen*` from `jacocoNonJvmTestableSurface` (now genuinely JVM-covered); the bucket comment now documents the shrink-one-screen-at-a-time mechanism.
## Not in this PR (deferred)
- The remaining ~26 excluded Compose UI files are converted in **per-area tickets under #373** (format controls, onboarding/lock, account setup, mail lists, settings, reader, compose editor, mailbox, app shell).
- **The JaCoCo floor re-ratchet is the final step of #373**, after all conversions are stable — deliberately NOT touched here (floor stays `0.79`).
- Existing instrumented `androidTest` Compose tests are kept as-is (the instrumented E2E stays).
## Gate
`assembleDebug` + `testDebugUnitTest` + `jacocoTestCoverageVerification` + `compileDebugAndroidTestKotlin` + `lintDebug` + `ktlintCheck` + `detekt` — all green locally.
**No auto-merge** (per spike instructions).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Lands the infrastructure + a runnable PoC for Robolectric-backed JVM unit testing of Jetpack Compose UI (umbrella #373). Robolectric runs the Android framework on the JVM, so
createComposeRule()can drive composables with no emulator — bringing render-only screens into the JaCoCo JVM-testable + counted surface, so thejacocoNonJvmTestableSurfaceexclusions can shrink.Feasibility: proven on this exact stack
Verified on AGP 9.2 (built-in Kotlin), Kotlin 2.4.0, Compose BOM 2026.06.00, JDK 21, JUnit4:
AddAnotherAccountScreenJvmTest— 3 tests, green on:app:testDebugUnitTest, no emulator. First test ~5s (Robolectric + NATIVE-graphics warmup), the rest <0.1s each.androidx.compose.ui.test.junit4.v2.createComposeRule(v1 is deprecated in this BOM).Changes
testscope + Composeui-test-junit4/ui-test-manifest(reusing the BOM the app already applies).testOptions.unitTests.isIncludeAndroidResources = truesostringResource(...)+ Material3 theming resolve on the JVM.app/src/test/resources/robolectric.propertiespinssdk=36— targetSdk 37 is a preview level Robolectric 4.16 ships no sandbox for (API 37 unsupported).AddAnotherAccountScreenJvmTest(@RunWith(RobolectricTestRunner)+@GraphicsMode(NATIVE)+@Config(sdk = [36])).**/AddAnotherAccountScreen*fromjacocoNonJvmTestableSurface(now genuinely JVM-covered); the bucket comment now documents the shrink-one-screen-at-a-time mechanism.Not in this PR (deferred)
0.79).androidTestCompose tests are kept as-is (the instrumented E2E stays).Gate
assembleDebug+testDebugUnitTest+jacocoTestCoverageVerification+compileDebugAndroidTestKotlin+lintDebug+ktlintCheck+detekt— all green locally.No auto-merge (per spike instructions).
🤖 Generated with Claude Code