chore(ui): migrate LocalClipboardManager to LocalClipboard

LocalClipboardManager/ClipboardManager are deprecated in Compose in favor
of LocalClipboard's suspend Clipboard API. Migrates the one call site,
ReportReviewScreen's "Copy report" action: LocalClipboardManager.current
becomes LocalClipboard.current, and the synchronous
clipboard.setText(AnnotatedString(...)) becomes a suspend
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(...))) run inside
the existing rememberCoroutineScope(). The clipboard interaction is
pulled into a small internal suspend function, copyReportPayloadToClipboard,
so it's unit-testable against a mocked Clipboard without an emulator.

Closes #237.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 00:16:32 -05:00
co-authored by Claude Opus 4.8
parent e587ba0d82
commit 191e802eac
3 changed files with 98 additions and 5 deletions
@@ -1,12 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package org.libremail.ui.reporting
import android.content.ClipboardManager
import android.content.Context
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
@@ -17,6 +19,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.every
import io.mockk.mockk
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -123,6 +126,23 @@ class ReportReviewScreenTest {
composeTestRule.onNodeWithText(string(R.string.report_submit)).performScrollTo().assertIsEnabled()
}
@Test
fun tappingCopy_putsThePayloadOnTheSystemClipboard_andShowsAConfirmation() {
// Exercises the #237 migration off LocalClipboardManager/ClipboardManager end-to-end: the
// real system clipboard (not a fake) must contain the exact payload after the suspend
// LocalClipboard/Clipboard call completes.
val store = setContent()
composeTestRule.onNodeWithText(string(R.string.report_copy)).performScrollTo().performClick()
composeTestRule.waitUntil(5_000) {
composeTestRule.onAllNodesWithText(string(R.string.report_copied)).fetchSemanticsNodes().isNotEmpty()
}
val clipboardManager = context.getSystemService(ClipboardManager::class.java)
val clipText = clipboardManager?.primaryClip?.getItemAt(0)?.text?.toString()
assertEquals(store.find(reportId)!!.toSubmissionPayload(), clipText)
}
@Test
fun tappingDiscard_deletesReport_andLeavesTheScreen() {
var done = false