The synchronous clipboard.setText(AnnotatedString(viewModel.payload())) → the suspend clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(label, payload))), run inside the screen's existing rememberCoroutineScope() (same scope.launch that already showed the "copied" snackbar, now sequenced: copy first, then snackbar — same order/behavior as before).
The clipboard write is pulled into a small internal suspend fun copyReportPayloadToClipboard(clipboard: Clipboard, payload: String) so it can be unit-tested directly against a mocked Clipboard, without a Compose UI test or emulator.
Tests
Unit (new): ReportReviewClipboardTest — mocks Clipboard and statically mocks ClipData's newPlainText factory (same pattern ContactsPermissionManagerTest already uses for Uri), and asserts the exact payload text reaches ClipData.newPlainText and that the resulting ClipData is wrapped unmodified in the ClipEntry passed to setClipEntry.
Instrumented (new, added to the existing ReportReviewScreenTest): tappingCopy_putsThePayloadOnTheSystemClipboard_andShowsAConfirmation — taps "Copy report", waits for the "copied" snackbar, then reads the real Android ClipboardManager system service and asserts its content equals the report's toSubmissionPayload(). This is the end-to-end verification the ticket called out given the async copy changes the runtime surface.
Validation
A peer agent is using the only local emulator tonight, so per this ticket's instructions no emulator was booted locally. Validated locally instead with the compile/unit gate:
All green (BUILD SUCCESSFUL), including the new unit test passing and the new instrumented test compiling cleanly. CI's full matrix (including the E2E jobs) will run the new instrumented test.
Closes #237
## Call sites migrated
Only one usage existed in `app/src/main`: `ReportReviewScreen.kt`'s "Copy report" action.
- `LocalClipboardManager.current` → `LocalClipboard.current` (returns `androidx.compose.ui.platform.Clipboard`).
- The synchronous `clipboard.setText(AnnotatedString(viewModel.payload()))` → the suspend `clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(label, payload)))`, run inside the screen's existing `rememberCoroutineScope()` (same `scope.launch` that already showed the "copied" snackbar, now sequenced: copy first, then snackbar — same order/behavior as before).
- The clipboard write is pulled into a small `internal suspend fun copyReportPayloadToClipboard(clipboard: Clipboard, payload: String)` so it can be unit-tested directly against a mocked `Clipboard`, without a Compose UI test or emulator.
## Tests
- **Unit** (new): `ReportReviewClipboardTest` — mocks `Clipboard` and statically mocks `ClipData`'s `newPlainText` factory (same pattern `ContactsPermissionManagerTest` already uses for `Uri`), and asserts the exact payload text reaches `ClipData.newPlainText` and that the resulting `ClipData` is wrapped unmodified in the `ClipEntry` passed to `setClipEntry`.
- **Instrumented** (new, added to the existing `ReportReviewScreenTest`): `tappingCopy_putsThePayloadOnTheSystemClipboard_andShowsAConfirmation` — taps "Copy report", waits for the "copied" snackbar, then reads the **real** Android `ClipboardManager` system service and asserts its content equals the report's `toSubmissionPayload()`. This is the end-to-end verification the ticket called out given the async copy changes the runtime surface.
## Validation
A peer agent is using the only local emulator tonight, so per this ticket's instructions no emulator was booted locally. Validated locally instead with the compile/unit gate:
```
:app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:lintDebug :app:ktlintCheck :app:detekt
```
All green (`BUILD SUCCESSFUL`), including the new unit test passing and the new instrumented test compiling cleanly. CI's full matrix (including the E2E jobs) will run the new instrumented test.
🤖 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.
Closes #237
Call sites migrated
Only one usage existed in
app/src/main:ReportReviewScreen.kt's "Copy report" action.LocalClipboardManager.current→LocalClipboard.current(returnsandroidx.compose.ui.platform.Clipboard).clipboard.setText(AnnotatedString(viewModel.payload()))→ the suspendclipboard.setClipEntry(ClipEntry(ClipData.newPlainText(label, payload))), run inside the screen's existingrememberCoroutineScope()(samescope.launchthat already showed the "copied" snackbar, now sequenced: copy first, then snackbar — same order/behavior as before).internal suspend fun copyReportPayloadToClipboard(clipboard: Clipboard, payload: String)so it can be unit-tested directly against a mockedClipboard, without a Compose UI test or emulator.Tests
ReportReviewClipboardTest— mocksClipboardand statically mocksClipData'snewPlainTextfactory (same patternContactsPermissionManagerTestalready uses forUri), and asserts the exact payload text reachesClipData.newPlainTextand that the resultingClipDatais wrapped unmodified in theClipEntrypassed tosetClipEntry.ReportReviewScreenTest):tappingCopy_putsThePayloadOnTheSystemClipboard_andShowsAConfirmation— taps "Copy report", waits for the "copied" snackbar, then reads the real AndroidClipboardManagersystem service and asserts its content equals the report'stoSubmissionPayload(). This is the end-to-end verification the ticket called out given the async copy changes the runtime surface.Validation
A peer agent is using the only local emulator tonight, so per this ticket's instructions no emulator was booted locally. Validated locally instead with the compile/unit gate:
All green (
BUILD SUCCESSFUL), including the new unit test passing and the new instrumented test compiling cleanly. CI's full matrix (including the E2E jobs) will run the new instrumented test.🤖 Generated with Claude Code