Batch 7/9 of the Robolectric Compose JVM-test epic (#382, umbrella #373).
What
Adds ComposeScreenJvmTest (17 tests) — a Robolectric JVM port of the instrumented ComposeScreenTest for the email-editor ComposeScreen, driven via the v2 createComposeRule() under @RunWith(RobolectricTestRunner) + @GraphicsMode(NATIVE) + @Config(sdk = [36]), in the test (JVM) source set.
Drops **/ComposeScreen* from jacocoNonJvmTestableSurface in app/build.gradle.kts (replaced with a #382 conversion comment), so the screen's render/interaction code now counts toward the JVM-testable surface. Every other entry kept (append-discipline).
How
ComposeViewModel is large (7 collaborators, several Context/Room-backed), so it is mocked — mirroring the established AccountPickerScreenJvmTest / ManualSetupScreenJvmTest pattern — with its state / accounts / finished flows stubbed so every render/state branch is injectable and interactions are asserted by verifying they forward to the view model. A RESUMED lifecycle owner (also the back-press dispatcher owner) and a no-op ActivityResultRegistryOwner let collectAsStateWithLifecycle, the BackHandler, and the attachment/inline-image rememberLauncherForActivityResult compose on the JVM. The embedded RichTextBodyField (from RichTextEditor.kt, unit-tested by RichTextEditorTest and intentionally kept in scope) renders live; its toolbar accessibility labels and body-change plumbing are covered too.
Batch 7/9 of the Robolectric Compose JVM-test epic (#382, umbrella #373).
## What
- Adds `ComposeScreenJvmTest` (17 tests) — a Robolectric JVM port of the instrumented `ComposeScreenTest` for the email-editor `ComposeScreen`, driven via the v2 `createComposeRule()` under `@RunWith(RobolectricTestRunner)` + `@GraphicsMode(NATIVE)` + `@Config(sdk = [36])`, in the `test` (JVM) source set.
- Drops `**/ComposeScreen*` from `jacocoNonJvmTestableSurface` in `app/build.gradle.kts` (replaced with a `#382` conversion comment), so the screen's render/interaction code now counts toward the JVM-testable surface. Every other entry kept (append-discipline).
## How
`ComposeViewModel` is large (7 collaborators, several `Context`/Room-backed), so it is **mocked** — mirroring the established `AccountPickerScreenJvmTest` / `ManualSetupScreenJvmTest` pattern — with its `state` / `accounts` / `finished` flows stubbed so every render/state branch is injectable and interactions are asserted by verifying they forward to the view model. A RESUMED lifecycle owner (also the back-press dispatcher owner) and a no-op `ActivityResultRegistryOwner` let `collectAsStateWithLifecycle`, the `BackHandler`, and the attachment/inline-image `rememberLauncherForActivityResult` compose on the JVM. The embedded `RichTextBodyField` (from `RichTextEditor.kt`, unit-tested by `RichTextEditorTest` and intentionally kept in scope) renders live; its toolbar accessibility labels and body-change plumbing are covered too.
## Coverage
Covered: initial render + core fields/toolbar, single-account From, Send enable/disable + tap, sending overlay spinner, back/exit, field forwarding (to/subject/body), contact suggestions render + pick, attachment-prompt Yes/No, no-account + multi-account From dropdown + select, attachment chip render (inline hidden) + remove, Cc/Bcc collapse/expand + prefill, toolbar a11y labels + body-change flow, finished→onBack, error snackbar.
The instrumented `ComposeScreenTest` stays as the on-device E2E.
## Gate (all green, foreground)
`assembleDebug` + `testDebugUnitTest` + `jacocoTestCoverageVerification` + `compileDebugAndroidTestKotlin` + `lintDebug` + `ktlintCheck` + `detekt` — BUILD SUCCESSFUL.
JaCoCo floor **unchanged at 0.79** (the re-ratchet is the final #373 step); overall line coverage **0.8645**, `ComposeScreenKt` 206 covered / 25 missed.
Closes #382.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Tick the box to add this pull request to the merge queue (same as @mergifyio queue).
Queue this pull request
Tick the box to add this pull request to the merge queue (same as `@mergifyio queue`).
- [ ] Queue this pull request <!-- mergify:queue-control:queue -->
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.
Batch 7/9 of the Robolectric Compose JVM-test epic (#382, umbrella #373).
What
ComposeScreenJvmTest(17 tests) — a Robolectric JVM port of the instrumentedComposeScreenTestfor the email-editorComposeScreen, driven via the v2createComposeRule()under@RunWith(RobolectricTestRunner)+@GraphicsMode(NATIVE)+@Config(sdk = [36]), in thetest(JVM) source set.**/ComposeScreen*fromjacocoNonJvmTestableSurfaceinapp/build.gradle.kts(replaced with a#382conversion comment), so the screen's render/interaction code now counts toward the JVM-testable surface. Every other entry kept (append-discipline).How
ComposeViewModelis large (7 collaborators, severalContext/Room-backed), so it is mocked — mirroring the establishedAccountPickerScreenJvmTest/ManualSetupScreenJvmTestpattern — with itsstate/accounts/finishedflows stubbed so every render/state branch is injectable and interactions are asserted by verifying they forward to the view model. A RESUMED lifecycle owner (also the back-press dispatcher owner) and a no-opActivityResultRegistryOwnerletcollectAsStateWithLifecycle, theBackHandler, and the attachment/inline-imagerememberLauncherForActivityResultcompose on the JVM. The embeddedRichTextBodyField(fromRichTextEditor.kt, unit-tested byRichTextEditorTestand intentionally kept in scope) renders live; its toolbar accessibility labels and body-change plumbing are covered too.Coverage
Covered: initial render + core fields/toolbar, single-account From, Send enable/disable + tap, sending overlay spinner, back/exit, field forwarding (to/subject/body), contact suggestions render + pick, attachment-prompt Yes/No, no-account + multi-account From dropdown + select, attachment chip render (inline hidden) + remove, Cc/Bcc collapse/expand + prefill, toolbar a11y labels + body-change flow, finished→onBack, error snackbar.
The instrumented
ComposeScreenTeststays as the on-device E2E.Gate (all green, foreground)
assembleDebug+testDebugUnitTest+jacocoTestCoverageVerification+compileDebugAndroidTestKotlin+lintDebug+ktlintCheck+detekt— BUILD SUCCESSFUL.JaCoCo floor unchanged at 0.79 (the re-ratchet is the final #373 step); overall line coverage 0.8645,
ComposeScreenKt206 covered / 25 missed.Closes #382.
🤖 Generated with Claude Code
Tick the box to add this pull request to the merge queue (same as
@mergifyio queue).