[#792] Add Sending Screens
* [#804][Design system] Paddings - Dimens - Initial commit to ensure it meets our requirements - This solution is now in parallel with a simpler existing Paddings solution - Four spacing groups defined - Provides a way to define default and custom spacing values - Distinguished by screen size, but other metrics also available (e.g. orientation, aspect ratio, layout direction or screen shape) * Fix spacing value * Move spacings change logic to comment - We've decided to have only one regular spacing group for now, which is suitable for most of current phone devices * Move Dimens out of internal package * Link TODO for later Paddings remove * Link issue of Use Dimens across the app to TODO * [#792] Add sending screens - This extends the Send screen logic with several subscreens - A transaction submission connected to the Synchronizer - Some logic moved to Android specific class - System back navigation handler implemented - Styling with dimes introduced to all Send screens - New UI, Android specific, integration and screenshot tests added - Additionally we could consider implementing a proper SendState mechanism, as we do in e.g. BackupState * File issue of problematic pseudolocales texts inserted in Send.Form screen * Add scrolling to send view * Switch SendViewIntegrationTest away from TestSetup * Provide MockSynchronizer to integration test --------- Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
42e640b874
commit
543d5f2b59
|
@ -16,6 +16,6 @@ object ZecSendFixture {
|
||||||
suspend fun new(
|
suspend fun new(
|
||||||
address: String = ADDRESS,
|
address: String = ADDRESS,
|
||||||
amount: Zatoshi = AMOUNT,
|
amount: Zatoshi = AMOUNT,
|
||||||
message: Memo = MEMO
|
memo: Memo = MEMO
|
||||||
) = ZecSend(WalletAddress.Unified.new(address), amount, message)
|
) = ZecSend(WalletAddress.Unified.new(address), amount, memo)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
package co.electriccoin.zcash.ui.fixture
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.CloseableSynchronizer
|
||||||
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||||
|
import cash.z.ecc.android.sdk.model.Account
|
||||||
|
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||||
|
import cash.z.ecc.android.sdk.model.PendingTransaction
|
||||||
|
import cash.z.ecc.android.sdk.model.Transaction
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||||
|
import cash.z.ecc.android.sdk.model.TransactionRecipient
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
|
import cash.z.ecc.android.sdk.model.WalletBalance
|
||||||
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.android.sdk.type.AddressType
|
||||||
|
import cash.z.ecc.android.sdk.type.ConsensusMatchType
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mocked Synchronizer that can be used instead of the production SdkSynchronizer e.g. for tests.
|
||||||
|
*/
|
||||||
|
@Suppress("TooManyFunctions", "UNUSED_PARAMETER")
|
||||||
|
internal class MockSynchronizer : CloseableSynchronizer {
|
||||||
|
|
||||||
|
override val clearedTransactions: Flow<List<TransactionOverview>>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val latestBirthdayHeight: BlockHeight
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val latestHeight: BlockHeight
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val network: ZcashNetwork
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val networkHeight: StateFlow<BlockHeight?>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override var onChainErrorHandler: ((BlockHeight, BlockHeight) -> Any)?
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
set(value) {}
|
||||||
|
|
||||||
|
override var onCriticalErrorHandler: ((Throwable?) -> Boolean)?
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
set(value) {}
|
||||||
|
|
||||||
|
override var onProcessorErrorHandler: ((Throwable?) -> Boolean)?
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
set(value) {}
|
||||||
|
|
||||||
|
override var onSetupErrorHandler: ((Throwable?) -> Boolean)?
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
set(value) {}
|
||||||
|
|
||||||
|
override var onSubmissionErrorHandler: ((Throwable?) -> Boolean)?
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
set(value) {}
|
||||||
|
|
||||||
|
override val orchardBalances: StateFlow<WalletBalance?>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val pendingTransactions: Flow<List<PendingTransaction>>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val processorInfo: Flow<CompactBlockProcessor.ProcessorInfo>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val progress: Flow<Int>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val receivedTransactions: Flow<List<Transaction.Received>>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val saplingBalances: StateFlow<WalletBalance?>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val sentTransactions: Flow<List<Transaction.Sent>>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val status: Flow<Synchronizer.Status>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override val transparentBalances: StateFlow<WalletBalance?>
|
||||||
|
get() = error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMemos(transactionOverview: TransactionOverview): Flow<String> {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getNearestRewindHeight(height: BlockHeight): BlockHeight {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRecipients(transactionOverview: TransactionOverview): Flow<TransactionRecipient> {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getSaplingAddress(account: Account): String {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTransparentAddress(account: Account): String {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTransparentBalance(tAddr: String): WalletBalance {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getUnifiedAddress(account: Account): String {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isValidShieldedAddr(address: String): Boolean {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isValidTransparentAddr(address: String): Boolean {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isValidUnifiedAddr(address: String): Boolean {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun quickRewind() {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refreshUtxos(account: Account, since: BlockHeight): Int? {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rewindToNearestHeight(height: BlockHeight, alsoClearBlockCache: Boolean) {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method intentionally returns empty flow, as the PendingTransaction is only an SDK internal class.
|
||||||
|
*/
|
||||||
|
override fun sendToAddress(usk: UnifiedSpendingKey, amount: Zatoshi, toAddress: String, memo: String): Flow<PendingTransaction> {
|
||||||
|
return emptyFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shieldFunds(usk: UnifiedSpendingKey, memo: String): Flow<PendingTransaction> {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun validateAddress(address: String): AddressType {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun validateConsensusBranch(): ConsensusMatchType {
|
||||||
|
error("Intentionally not implemented in ${MockSynchronizer::class.simpleName} implementation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun new() = MockSynchronizer()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextClearance
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||||
|
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||||
|
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.clickBack() {
|
||||||
|
onNodeWithContentDescription(getStringResource(R.string.send_back_content_description)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setValidAmount() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
||||||
|
val separators = MonetarySeparators.current()
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput("123${separators.decimal}456")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setAmount(amount: String) {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setValidAddress() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_to)).also {
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput(WalletAddressFixture.UNIFIED_ADDRESS_STRING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setAddress(address: String) {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_to)).also {
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setValidMemo() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_memo)).also {
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput(MemoFixture.MEMO_STRING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.setMemo(memo: String) {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_memo)).also {
|
||||||
|
it.performTextClearance()
|
||||||
|
it.performTextInput(memo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.clickCreateAndSend() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_create)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.clickConfirmation() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_confirmation_button)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertOnForm() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_create)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertOnConfirmation() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_confirmation_button)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertOnSending() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_in_progress_wait)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertOnSendSuccessful() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_successful_title)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertOnSendFailure() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_failure_title)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertSendEnabled() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_create)).also {
|
||||||
|
it.assertIsEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.assertSendDisabled() {
|
||||||
|
onNodeWithText(getStringResource(R.string.send_create)).also {
|
||||||
|
it.assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
|
import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
|
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class SendViewTestSetup(
|
||||||
|
private val composeTestRule: ComposeContentTestRule,
|
||||||
|
private val initialState: SendStage,
|
||||||
|
private val initialZecSend: ZecSend?
|
||||||
|
) {
|
||||||
|
private val onBackCount = AtomicInteger(0)
|
||||||
|
private val onCreateCount = AtomicInteger(0)
|
||||||
|
val mutableActionExecuted = MutableStateFlow(false)
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var lastSendStage: SendStage? = null
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var lastZecSend: ZecSend? = null
|
||||||
|
|
||||||
|
fun getOnBackCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onBackCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnCreateCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onCreateCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastZecSend(): ZecSend? {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return lastZecSend
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLastSendStage(): SendStage? {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return lastSendStage
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
|
fun DefaultContent() {
|
||||||
|
val (sendStage, setSendStage) =
|
||||||
|
rememberSaveable { mutableStateOf(initialState) }
|
||||||
|
|
||||||
|
lastSendStage = sendStage
|
||||||
|
|
||||||
|
val onBackAction = {
|
||||||
|
onBackCount.incrementAndGet()
|
||||||
|
when (sendStage) {
|
||||||
|
SendStage.Form -> {}
|
||||||
|
SendStage.Confirmation -> setSendStage(SendStage.Form)
|
||||||
|
SendStage.Sending -> {}
|
||||||
|
SendStage.SendFailure -> setSendStage(SendStage.Form)
|
||||||
|
SendStage.SendSuccessful -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
onBackAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
val (zecSend, setZecSend) =
|
||||||
|
rememberSaveable(stateSaver = ZecSend.Saver) { mutableStateOf(initialZecSend) }
|
||||||
|
|
||||||
|
lastZecSend = zecSend
|
||||||
|
|
||||||
|
ZcashTheme {
|
||||||
|
Send(
|
||||||
|
mySpendableBalance = ZatoshiFixture.new(),
|
||||||
|
sendStage = sendStage,
|
||||||
|
onSendStageChange = setSendStage,
|
||||||
|
zecSend = zecSend,
|
||||||
|
onZecSendChange = setZecSend,
|
||||||
|
onBack = onBackAction,
|
||||||
|
onCreateAndSend = {
|
||||||
|
onCreateCount.incrementAndGet()
|
||||||
|
lastZecSend = it
|
||||||
|
mutableActionExecuted.update { true }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDefaultContent() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
DefaultContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send.ext
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.test.getAppContext
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class MemoExtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
fun value_or_empty_char_test_empty_input() {
|
||||||
|
val actual = MemoFixture.new(memoString = "").valueOrEmptyChar(getAppContext())
|
||||||
|
|
||||||
|
val expected = getAppContext().getString(R.string.empty_char)
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
fun value_or_empty_char_test_non_empty_input() {
|
||||||
|
val actual = MemoFixture.new(memoString = MemoFixture.MEMO_STRING).valueOrEmptyChar(getAppContext())
|
||||||
|
|
||||||
|
val expected = MemoFixture.MEMO_STRING
|
||||||
|
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send.integration
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.assertTextContains
|
||||||
|
import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import cash.z.ecc.android.sdk.fixture.WalletFixture
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||||
|
import cash.z.ecc.sdk.fixture.ZecSendFixture
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.fixture.MockSynchronizer
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickBack
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setAddress
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setAmount
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setMemo
|
||||||
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SendViewIntegrationTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
private val wallet = WalletFixture.Alice
|
||||||
|
private val network = ZcashNetwork.Testnet
|
||||||
|
private val spendingKey = runBlocking {
|
||||||
|
WalletFixture.Alice.getUnifiedSpendingKey(
|
||||||
|
seed = wallet.seedPhrase,
|
||||||
|
network = network
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val synchronizer = MockSynchronizer.new()
|
||||||
|
private val balance = ZatoshiFixture.new()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun send_screens_values_state_restoration() {
|
||||||
|
val restorationTester = StateRestorationTester(composeTestRule)
|
||||||
|
|
||||||
|
val expectedAmount = ZecSendFixture.AMOUNT.value
|
||||||
|
val expectedAddress = ZecSendFixture.ADDRESS
|
||||||
|
val expectedMemo = ZecSendFixture.MEMO.value
|
||||||
|
|
||||||
|
restorationTester.setContent {
|
||||||
|
WrapSend(
|
||||||
|
synchronizer = synchronizer,
|
||||||
|
spendableBalance = balance,
|
||||||
|
spendingKey = spendingKey,
|
||||||
|
goBack = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill form
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
composeTestRule.setAddress(expectedAddress)
|
||||||
|
composeTestRule.setAmount(expectedAmount.toString())
|
||||||
|
composeTestRule.setMemo(expectedMemo)
|
||||||
|
|
||||||
|
// Move to confirmation
|
||||||
|
composeTestRule.clickCreateAndSend()
|
||||||
|
composeTestRule.assertOnConfirmation()
|
||||||
|
|
||||||
|
restorationTester.emulateSavedInstanceStateRestore()
|
||||||
|
|
||||||
|
// Check if stage recreated correctly
|
||||||
|
composeTestRule.assertOnConfirmation()
|
||||||
|
|
||||||
|
// Move back to form
|
||||||
|
composeTestRule.clickBack()
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
// And check recreated form values too
|
||||||
|
// We use that the assertTextContains searches in SemanticsProperties.EditableText too
|
||||||
|
// Note also that we don't check the amount field value, as it's changed by validation mechanisms
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_to)).also {
|
||||||
|
it.assertTextContains(ZecSendFixture.ADDRESS)
|
||||||
|
}
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_memo)).also {
|
||||||
|
it.assertTextContains(ZecSendFixture.MEMO.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send.view
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
|
import cash.z.ecc.sdk.fixture.ZecSendFixture
|
||||||
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSendFailure
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSendSuccessful
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSending
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
// Non-multiplatform tests that require interacting with the Android system (e.g. system back navigation)
|
||||||
|
// These don't have persistent state, so they are still unit tests.
|
||||||
|
class SendViewAndroidTest : UiTestPrerequisites() {
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
private fun newTestSetup(
|
||||||
|
sendStage: SendStage = SendStage.Form,
|
||||||
|
zecSend: ZecSend? = null
|
||||||
|
) = SendViewTestSetup(
|
||||||
|
composeTestRule,
|
||||||
|
sendStage,
|
||||||
|
zecSend
|
||||||
|
).apply {
|
||||||
|
setDefaultContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_sending_with_system_navigation_disabled_check() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.Confirmation,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnConfirmation()
|
||||||
|
composeTestRule.clickConfirmation()
|
||||||
|
composeTestRule.assertOnSending()
|
||||||
|
|
||||||
|
Espresso.pressBack()
|
||||||
|
|
||||||
|
composeTestRule.assertOnSending()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_send_successful_with_system_navigation() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendSuccessful,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnSendSuccessful()
|
||||||
|
|
||||||
|
Espresso.pressBack()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_send_failure_with_system_navigation() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendFailure,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnSendFailure()
|
||||||
|
|
||||||
|
Espresso.pressBack()
|
||||||
|
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
package co.electriccoin.zcash.ui.screen.send.view
|
package co.electriccoin.zcash.ui.screen.send.view
|
||||||
|
|
||||||
import androidx.compose.ui.test.assertIsEnabled
|
|
||||||
import androidx.compose.ui.test.assertIsNotEnabled
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTextClearance
|
|
||||||
import androidx.compose.ui.test.performTextInput
|
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import cash.z.ecc.android.sdk.ext.collectWith
|
import cash.z.ecc.android.sdk.ext.collectWith
|
||||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||||
|
@ -16,22 +12,35 @@ import cash.z.ecc.android.sdk.model.Memo
|
||||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||||
import cash.z.ecc.android.sdk.model.Zatoshi
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.model.ZecSend
|
import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
import cash.z.ecc.sdk.fixture.MemoFixture
|
|
||||||
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
|
||||||
import cash.z.ecc.sdk.fixture.ZecRequestFixture
|
import cash.z.ecc.sdk.fixture.ZecRequestFixture
|
||||||
|
import cash.z.ecc.sdk.fixture.ZecSendFixture
|
||||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSendFailure
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSendSuccessful
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertOnSending
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertSendDisabled
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.assertSendEnabled
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickBack
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setAmount
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setMemo
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setValidAddress
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setValidAmount
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.setValidMemo
|
||||||
import co.electriccoin.zcash.ui.test.getStringResource
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
@ -40,11 +49,22 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val composeTestRule = createComposeRule()
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
private fun newTestSetup(
|
||||||
|
sendStage: SendStage = SendStage.Form,
|
||||||
|
zecSend: ZecSend? = null
|
||||||
|
) = SendViewTestSetup(
|
||||||
|
composeTestRule,
|
||||||
|
sendStage,
|
||||||
|
zecSend
|
||||||
|
).apply {
|
||||||
|
setDefaultContent()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun create_button_disabled() {
|
fun create_button_disabled() {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_create)).also {
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_create)).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
|
@ -56,10 +76,10 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun create_request_no_memo() = runTest {
|
fun create_request_no_memo() = runTest {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnCreateCount())
|
assertEquals(0, testSetup.getOnCreateCount())
|
||||||
assertEquals(null, testSetup.getLastSend())
|
assertEquals(null, testSetup.getLastZecSend())
|
||||||
|
|
||||||
composeTestRule.setValidAmount()
|
composeTestRule.setValidAmount()
|
||||||
composeTestRule.setValidAddress()
|
composeTestRule.setValidAddress()
|
||||||
|
@ -74,7 +94,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
assertEquals(1, testSetup.getOnCreateCount())
|
assertEquals(1, testSetup.getOnCreateCount())
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
testSetup.getLastSend().also {
|
testSetup.getLastZecSend().also {
|
||||||
assertNotNull(it)
|
assertNotNull(it)
|
||||||
assertEquals(WalletAddressFixture.unified(), it.destination)
|
assertEquals(WalletAddressFixture.unified(), it.destination)
|
||||||
assertEquals(Zatoshi(12345678900000), it.amount)
|
assertEquals(Zatoshi(12345678900000), it.amount)
|
||||||
|
@ -90,10 +110,10 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun create_request_with_memo() = runTest {
|
fun create_request_with_memo() = runTest {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnCreateCount())
|
assertEquals(0, testSetup.getOnCreateCount())
|
||||||
assertEquals(null, testSetup.getLastSend())
|
assertEquals(null, testSetup.getLastZecSend())
|
||||||
|
|
||||||
composeTestRule.setValidAmount()
|
composeTestRule.setValidAmount()
|
||||||
composeTestRule.setValidAddress()
|
composeTestRule.setValidAddress()
|
||||||
|
@ -110,7 +130,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
assertEquals(1, testSetup.getOnCreateCount())
|
assertEquals(1, testSetup.getOnCreateCount())
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
testSetup.getLastSend().also {
|
testSetup.getLastZecSend().also {
|
||||||
assertNotNull(it)
|
assertNotNull(it)
|
||||||
assertEquals(WalletAddressFixture.unified(), it.destination)
|
assertEquals(WalletAddressFixture.unified(), it.destination)
|
||||||
assertEquals(Zatoshi(12345678900000), it.amount)
|
assertEquals(Zatoshi(12345678900000), it.amount)
|
||||||
|
@ -126,11 +146,11 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun check_regex_functionality_valid_inputs() = runTest {
|
fun check_regex_functionality_valid_inputs() = runTest {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
val separators = MonetarySeparators.current()
|
val separators = MonetarySeparators.current()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnCreateCount())
|
assertEquals(0, testSetup.getOnCreateCount())
|
||||||
assertEquals(null, testSetup.getLastSend())
|
assertEquals(null, testSetup.getLastZecSend())
|
||||||
composeTestRule.assertSendDisabled()
|
composeTestRule.assertSendDisabled()
|
||||||
|
|
||||||
composeTestRule.setValidAmount()
|
composeTestRule.setValidAmount()
|
||||||
|
@ -168,7 +188,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
assertEquals(1, testSetup.getOnCreateCount())
|
assertEquals(1, testSetup.getOnCreateCount())
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
testSetup.getLastSend().also {
|
testSetup.getLastZecSend().also {
|
||||||
assertNotNull(it)
|
assertNotNull(it)
|
||||||
assertEquals(WalletAddressFixture.unified(), it.destination)
|
assertEquals(WalletAddressFixture.unified(), it.destination)
|
||||||
assertEquals(Zatoshi(12345678900000), it.amount)
|
assertEquals(Zatoshi(12345678900000), it.amount)
|
||||||
|
@ -184,11 +204,11 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun check_regex_functionality_invalid_inputs() = runTest {
|
fun check_regex_functionality_invalid_inputs() = runTest {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
val separators = MonetarySeparators.current()
|
val separators = MonetarySeparators.current()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnCreateCount())
|
assertEquals(0, testSetup.getOnCreateCount())
|
||||||
assertEquals(null, testSetup.getLastSend())
|
assertEquals(null, testSetup.getLastZecSend())
|
||||||
composeTestRule.assertSendDisabled()
|
composeTestRule.assertSendDisabled()
|
||||||
|
|
||||||
composeTestRule.setAmount("aaa")
|
composeTestRule.setAmount("aaa")
|
||||||
|
@ -214,7 +234,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
composeTestRule.assertSendDisabled()
|
composeTestRule.assertSendDisabled()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnCreateCount())
|
assertEquals(0, testSetup.getOnCreateCount())
|
||||||
assertEquals(null, testSetup.getLastSend())
|
assertEquals(null, testSetup.getLastZecSend())
|
||||||
composeTestRule.assertSendDisabled()
|
composeTestRule.assertSendDisabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +242,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun max_memo_length() = runTest {
|
fun max_memo_length() = runTest {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
composeTestRule.setValidAmount()
|
composeTestRule.setValidAmount()
|
||||||
composeTestRule.setValidAddress()
|
composeTestRule.setValidAddress()
|
||||||
|
@ -246,7 +266,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
assertEquals(1, testSetup.getOnCreateCount())
|
assertEquals(1, testSetup.getOnCreateCount())
|
||||||
|
|
||||||
launch {
|
launch {
|
||||||
testSetup.getLastSend().also {
|
testSetup.getLastZecSend().also {
|
||||||
assertNotNull(it)
|
assertNotNull(it)
|
||||||
assertEquals(WalletAddressFixture.unified(), it.destination)
|
assertEquals(WalletAddressFixture.unified(), it.destination)
|
||||||
assertEquals(Zatoshi(12345600000), it.amount)
|
assertEquals(Zatoshi(12345600000), it.amount)
|
||||||
|
@ -261,7 +281,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun back_on_form() {
|
fun back_on_form() {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
@ -273,7 +293,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun back_on_confirmation() {
|
fun back_on_confirmation() {
|
||||||
val testSetup = TestSetup(composeTestRule)
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
@ -284,127 +304,95 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
composeTestRule.clickBack()
|
composeTestRule.clickBack()
|
||||||
composeTestRule.assertOnForm()
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_sending_disabled_check() {
|
||||||
|
newTestSetup(
|
||||||
|
SendStage.Confirmation,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule.assertOnConfirmation()
|
||||||
|
composeTestRule.clickConfirmation()
|
||||||
|
composeTestRule.assertOnSending()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.send_back_content_description)).also {
|
||||||
|
it.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_send_successful() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendSuccessful,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnSendSuccessful()
|
||||||
|
composeTestRule.clickBack()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule) {
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun close_on_send_successful() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendSuccessful,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
private val onBackCount = AtomicInteger(0)
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
private val onCreateCount = AtomicInteger(0)
|
|
||||||
val mutableActionExecuted = MutableStateFlow(false)
|
|
||||||
|
|
||||||
@Volatile
|
composeTestRule.assertOnSendSuccessful()
|
||||||
private var onSendZecRequest: ZecSend? = null
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_successful_button)).also {
|
||||||
|
it.assertExists()
|
||||||
fun getOnBackCount(): Int {
|
it.performClick()
|
||||||
composeTestRule.waitForIdle()
|
|
||||||
return onBackCount.get()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnCreateCount(): Int {
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
composeTestRule.waitForIdle()
|
}
|
||||||
return onCreateCount.get()
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun back_on_send_failure() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendFailure,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnSendFailure()
|
||||||
|
composeTestRule.clickBack()
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun close_on_send_failure() {
|
||||||
|
val testSetup = newTestSetup(
|
||||||
|
SendStage.SendFailure,
|
||||||
|
runBlocking { ZecSendFixture.new() }
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
|
composeTestRule.assertOnSendFailure()
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_failure_button)).also {
|
||||||
|
it.assertExists()
|
||||||
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
fun getLastSend(): ZecSend? {
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
composeTestRule.waitForIdle()
|
|
||||||
return onSendZecRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
composeTestRule.setContent {
|
|
||||||
ZcashTheme {
|
|
||||||
Send(
|
|
||||||
mySpendableBalance = ZatoshiFixture.new(),
|
|
||||||
goBack = {
|
|
||||||
onBackCount.incrementAndGet()
|
|
||||||
},
|
|
||||||
onCreateAndSend = {
|
|
||||||
onCreateCount.incrementAndGet()
|
|
||||||
onSendZecRequest = it
|
|
||||||
mutableActionExecuted.update { true }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickBack() {
|
|
||||||
onNodeWithContentDescription(getStringResource(R.string.send_back_content_description)).also {
|
|
||||||
it.performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.setValidAmount() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
|
||||||
val separators = MonetarySeparators.current()
|
|
||||||
it.performTextClearance()
|
|
||||||
it.performTextInput("123${separators.decimal}456")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.setAmount(amount: String) {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
|
||||||
it.performTextClearance()
|
|
||||||
it.performTextInput(amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.setValidAddress() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_to)).also {
|
|
||||||
it.performTextClearance()
|
|
||||||
it.performTextInput(WalletAddressFixture.UNIFIED_ADDRESS_STRING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.setValidMemo() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_memo)).also {
|
|
||||||
it.performTextClearance()
|
|
||||||
it.performTextInput(MemoFixture.MEMO_STRING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.setMemo(memo: String) {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_memo)).also {
|
|
||||||
it.performTextClearance()
|
|
||||||
it.performTextInput(memo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickCreateAndSend() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_create)).also {
|
|
||||||
it.performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickConfirmation() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_confirm)).also {
|
|
||||||
it.performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.assertOnForm() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_create)).also {
|
|
||||||
it.assertExists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.assertOnConfirmation() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_confirm)).also {
|
|
||||||
it.assertExists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.assertSendEnabled() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_create)).also {
|
|
||||||
it.assertIsEnabled()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.assertSendDisabled() {
|
|
||||||
onNodeWithText(getStringResource(R.string.send_create)).also {
|
|
||||||
it.assertIsNotEnabled()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("ktlint:filename")
|
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.screen.scan
|
package co.electriccoin.zcash.ui.screen.scan
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
|
|
@ -3,14 +3,27 @@
|
||||||
package co.electriccoin.zcash.ui.screen.send
|
package co.electriccoin.zcash.ui.screen.send
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
import cash.z.ecc.android.sdk.model.UnifiedSpendingKey
|
||||||
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
|
import cash.z.ecc.android.sdk.model.isFailedSubmit
|
||||||
|
import cash.z.ecc.android.sdk.model.isSubmitSuccess
|
||||||
import cash.z.ecc.sdk.send
|
import cash.z.ecc.sdk.send
|
||||||
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
||||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@ -27,23 +40,76 @@ private fun WrapSend(
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
val spendableBalance = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value?.spendableBalance()
|
val spendableBalance = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value?.spendableBalance()
|
||||||
|
|
||||||
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
|
WrapSend(synchronizer, spendableBalance, spendingKey, goBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Composable
|
||||||
|
internal fun WrapSend(
|
||||||
|
synchronizer: Synchronizer?,
|
||||||
|
spendableBalance: Zatoshi?,
|
||||||
|
spendingKey: UnifiedSpendingKey?,
|
||||||
|
goBack: () -> Unit
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// For now, we're avoiding sub-navigation to keep the navigation logic simple. But this might
|
||||||
|
// change once deep-linking support is added. It depends on whether deep linking should do one of:
|
||||||
|
// 1. Use a different UI flow entirely
|
||||||
|
// 2. Show a pre-filled Send form
|
||||||
|
// 3. Go directly to the Confirmation screen
|
||||||
|
val (sendStage, setSendStage) = rememberSaveable { mutableStateOf(SendStage.Form) }
|
||||||
|
|
||||||
|
val (zecSend, setZecSend) = rememberSaveable(stateSaver = ZecSend.Saver) { mutableStateOf(null) }
|
||||||
|
|
||||||
|
val onBackAction = {
|
||||||
|
when (sendStage) {
|
||||||
|
SendStage.Form -> goBack()
|
||||||
|
SendStage.Confirmation -> setSendStage(SendStage.Form)
|
||||||
|
SendStage.Sending -> { /* no action - wait until done */ }
|
||||||
|
SendStage.SendFailure -> setSendStage(SendStage.Form)
|
||||||
|
SendStage.SendSuccessful -> goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
onBackAction()
|
||||||
|
}
|
||||||
|
|
||||||
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
||||||
// Display loading indicator
|
// Display loading indicator
|
||||||
} else {
|
} else {
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = spendableBalance,
|
mySpendableBalance = spendableBalance,
|
||||||
goBack = goBack,
|
sendStage = sendStage,
|
||||||
|
onSendStageChange = setSendStage,
|
||||||
|
zecSend = zecSend,
|
||||||
|
onZecSendChange = setZecSend,
|
||||||
|
onBack = onBackAction,
|
||||||
onCreateAndSend = {
|
onCreateAndSend = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
synchronizer.send(spendingKey, it)
|
synchronizer.send(spendingKey, it).collect {
|
||||||
goBack()
|
Twig.debug { "Sending transaction id: ${it.id}" }
|
||||||
|
|
||||||
|
if (it.isSubmitSuccess()) {
|
||||||
|
setSendStage(SendStage.SendSuccessful)
|
||||||
|
Twig.debug {
|
||||||
|
"Transaction id:${it.id} submitted successfully at ${it.createTime} with " +
|
||||||
|
"${it.submitAttempts} attempts."
|
||||||
|
}
|
||||||
|
} else if (it.isFailedSubmit()) {
|
||||||
|
Twig.debug { "Transaction id:${it.id} submission failed with: ${it.errorMessage}." }
|
||||||
|
setSendStage(SendStage.SendFailure)
|
||||||
|
}
|
||||||
|
// All other states of Pending transaction mean waiting for one of the states above
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send.ext
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import cash.z.ecc.android.sdk.model.Memo
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
internal fun Memo.valueOrEmptyChar(): String {
|
||||||
|
LocalConfiguration.current
|
||||||
|
return valueOrEmptyChar(LocalContext.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Memo.valueOrEmptyChar(context: Context): String {
|
||||||
|
return value.ifEmpty { context.getString(R.string.empty_char) }
|
||||||
|
}
|
|
@ -26,5 +26,5 @@ internal fun WalletAddress.abbreviated(context: Context): String {
|
||||||
val firstFive = address.substring(0, ABBREVIATION_INDEX)
|
val firstFive = address.substring(0, ABBREVIATION_INDEX)
|
||||||
val lastFive = address.substring(address.length - ABBREVIATION_INDEX, address.length)
|
val lastFive = address.substring(address.length - ABBREVIATION_INDEX, address.length)
|
||||||
|
|
||||||
return context.getString(R.string.send_abbreviated_address_format, firstFive, lastFive)
|
return context.getString(R.string.send_confirmation_abbreviated_address_format, firstFive, lastFive)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,8 @@ package co.electriccoin.zcash.ui.screen.send.model
|
||||||
|
|
||||||
enum class SendStage {
|
enum class SendStage {
|
||||||
Form,
|
Form,
|
||||||
Confirmation
|
Confirmation,
|
||||||
|
Sending,
|
||||||
|
SendFailure,
|
||||||
|
SendSuccessful
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,8 +48,8 @@ import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -59,47 +59,47 @@ fun PreviewSend() {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = ZatoshiFixture.new(),
|
mySpendableBalance = ZatoshiFixture.new(),
|
||||||
goBack = {},
|
sendStage = SendStage.Form,
|
||||||
onCreateAndSend = {}
|
onSendStageChange = {},
|
||||||
|
zecSend = null,
|
||||||
|
onZecSendChange = {},
|
||||||
|
onCreateAndSend = {},
|
||||||
|
onBack = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Send(
|
fun Send(
|
||||||
mySpendableBalance: Zatoshi,
|
mySpendableBalance: Zatoshi,
|
||||||
goBack: () -> Unit,
|
sendStage: SendStage,
|
||||||
|
onSendStageChange: (SendStage) -> Unit,
|
||||||
|
zecSend: ZecSend?,
|
||||||
|
onZecSendChange: (ZecSend) -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
onCreateAndSend: (ZecSend) -> Unit
|
onCreateAndSend: (ZecSend) -> Unit
|
||||||
) {
|
) {
|
||||||
// For now, we're avoiding sub-navigation to keep the navigation logic simple. But this might
|
|
||||||
// change once deep-linking support is added. It depends on whether deep linking should do one of:
|
|
||||||
// 1. Use a different UI flow entirely
|
|
||||||
// 2. Show a pre-filled Send form
|
|
||||||
// 3. Go directly to the press-and-hold confirmation
|
|
||||||
val (sendStage, setSendStage) = rememberSaveable { mutableStateOf(SendStage.Form) }
|
|
||||||
|
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
SendTopAppBar(onBack = {
|
SendTopAppBar(
|
||||||
when (sendStage) {
|
onBack = onBack,
|
||||||
SendStage.Form -> goBack()
|
showBackNavigationButton = sendStage != SendStage.Sending
|
||||||
SendStage.Confirmation -> setSendStage(SendStage.Form)
|
)
|
||||||
}
|
|
||||||
})
|
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
SendMainContent(
|
SendMainContent(
|
||||||
myBalance = mySpendableBalance,
|
myBalance = mySpendableBalance,
|
||||||
|
onBack = onBack,
|
||||||
sendStage = sendStage,
|
sendStage = sendStage,
|
||||||
setSendStage = setSendStage,
|
onSendStageChange = onSendStageChange,
|
||||||
onCreateAndSend = onCreateAndSend,
|
zecSend = zecSend,
|
||||||
|
onZecSendChange = onZecSendChange,
|
||||||
|
onSendSubmit = onCreateAndSend,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.verticalScroll(
|
|
||||||
rememberScrollState()
|
|
||||||
)
|
|
||||||
.padding(
|
.padding(
|
||||||
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
|
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
|
||||||
bottom = dimens.spacingDefault,
|
bottom = paddingValues.calculateBottomPadding() + dimens.spacingDefault,
|
||||||
start = dimens.spacingDefault,
|
start = dimens.spacingDefault,
|
||||||
end = dimens.spacingDefault
|
end = dimens.spacingDefault
|
||||||
)
|
)
|
||||||
|
@ -109,51 +109,83 @@ fun Send(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
private fun SendTopAppBar(onBack: () -> Unit) {
|
private fun SendTopAppBar(
|
||||||
TopAppBar(
|
onBack: () -> Unit,
|
||||||
title = { Text(text = stringResource(id = R.string.send_title)) },
|
showBackNavigationButton: Boolean = true
|
||||||
navigationIcon = {
|
) {
|
||||||
IconButton(
|
if (showBackNavigationButton) {
|
||||||
onClick = onBack
|
TopAppBar(
|
||||||
) {
|
title = { Text(text = stringResource(id = R.string.send_title)) },
|
||||||
Icon(
|
navigationIcon = {
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
IconButton(
|
||||||
contentDescription = stringResource(R.string.send_back_content_description)
|
onClick = onBack
|
||||||
)
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.send_back_content_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
} else {
|
||||||
|
TopAppBar(title = { Text(text = stringResource(id = R.string.send_title)) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@Composable
|
@Composable
|
||||||
private fun SendMainContent(
|
private fun SendMainContent(
|
||||||
myBalance: Zatoshi,
|
myBalance: Zatoshi,
|
||||||
|
zecSend: ZecSend?,
|
||||||
|
onZecSendChange: (ZecSend) -> Unit,
|
||||||
|
onBack: () -> Unit,
|
||||||
sendStage: SendStage,
|
sendStage: SendStage,
|
||||||
setSendStage: (SendStage) -> Unit,
|
onSendStageChange: (SendStage) -> Unit,
|
||||||
onCreateAndSend: (ZecSend) -> Unit,
|
onSendSubmit: (ZecSend) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val (zecSend, setZecSend) = rememberSaveable(stateSaver = ZecSend.Saver) { mutableStateOf(null) }
|
when {
|
||||||
|
(sendStage == SendStage.Form || null == zecSend) -> {
|
||||||
if (sendStage == SendStage.Form || null == zecSend) {
|
SendForm(
|
||||||
SendForm(
|
myBalance = myBalance,
|
||||||
myBalance = myBalance,
|
previousZecSend = zecSend,
|
||||||
previousZecSend = zecSend,
|
onCreateZecSend = {
|
||||||
onCreateAndSend = {
|
onSendStageChange(SendStage.Confirmation)
|
||||||
setSendStage(SendStage.Confirmation)
|
onZecSendChange(it)
|
||||||
setZecSend(it)
|
},
|
||||||
},
|
modifier = modifier
|
||||||
modifier = modifier
|
)
|
||||||
)
|
}
|
||||||
} else {
|
(sendStage == SendStage.Confirmation) -> {
|
||||||
Confirmation(
|
Confirmation(
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onConfirmation = {
|
onConfirmation = {
|
||||||
onCreateAndSend(zecSend)
|
onSendStageChange(SendStage.Sending)
|
||||||
},
|
onSendSubmit(zecSend)
|
||||||
modifier = modifier
|
},
|
||||||
)
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(sendStage == SendStage.Sending) -> {
|
||||||
|
Sending(
|
||||||
|
zecSend = zecSend,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(sendStage == SendStage.SendSuccessful) -> {
|
||||||
|
SendSuccessful(
|
||||||
|
zecSend = zecSend,
|
||||||
|
modifier = modifier,
|
||||||
|
onDone = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(sendStage == SendStage.SendFailure) -> {
|
||||||
|
SendFailure(
|
||||||
|
zecSend = zecSend,
|
||||||
|
modifier = modifier,
|
||||||
|
onDone = onBack
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,11 +194,10 @@ private fun SendMainContent(
|
||||||
// TODO [#294]: DetektAll failed LongMethod
|
// TODO [#294]: DetektAll failed LongMethod
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
private fun SendForm(
|
private fun SendForm(
|
||||||
myBalance: Zatoshi,
|
myBalance: Zatoshi,
|
||||||
previousZecSend: ZecSend?,
|
previousZecSend: ZecSend?,
|
||||||
onCreateAndSend: (ZecSend) -> Unit,
|
onCreateZecSend: (ZecSend) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -190,6 +221,7 @@ private fun SendForm(
|
||||||
Column(
|
Column(
|
||||||
modifier
|
modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Header(
|
Header(
|
||||||
text = stringResource(id = R.string.send_balance, myBalance.toZecString()),
|
text = stringResource(id = R.string.send_balance, myBalance.toZecString()),
|
||||||
|
@ -268,7 +300,7 @@ private fun SendForm(
|
||||||
)
|
)
|
||||||
|
|
||||||
when (zecSendValidation) {
|
when (zecSendValidation) {
|
||||||
is ZecSendExt.ZecSendValidation.Valid -> onCreateAndSend(zecSendValidation.zecSend)
|
is ZecSendExt.ZecSendValidation.Valid -> onCreateZecSend(zecSendValidation.zecSend)
|
||||||
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
|
is ZecSendExt.ZecSendValidation.Invalid -> validation = zecSendValidation.validationErrors
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -286,18 +318,193 @@ private fun Confirmation(
|
||||||
onConfirmation: () -> Unit,
|
onConfirmation: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(modifier) {
|
Column(
|
||||||
Text(
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(
|
||||||
|
rememberScrollState()
|
||||||
|
)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
Body(
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.send_amount_and_address_format,
|
R.string.send_confirmation_amount_and_address_format,
|
||||||
zecSend.amount.toZecString(),
|
zecSend.amount.toZecString(),
|
||||||
zecSend.destination.abbreviated()
|
zecSend.destination.abbreviated()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if (zecSend.memo.value.isNotEmpty()) {
|
||||||
|
Body(
|
||||||
|
stringResource(
|
||||||
|
R.string.send_confirmation_memo_format,
|
||||||
|
zecSend.memo.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(MINIMAL_WEIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
|
modifier = Modifier.padding(top = dimens.spacingSmall),
|
||||||
onClick = onConfirmation,
|
onClick = onConfirmation,
|
||||||
text = stringResource(id = R.string.send_confirm)
|
text = stringResource(id = R.string.send_confirmation_button)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Sending(
|
||||||
|
zecSend: ZecSend,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(
|
||||||
|
rememberScrollState()
|
||||||
|
)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
Header(
|
||||||
|
text = stringResource(
|
||||||
|
R.string.send_in_progress_amount_format,
|
||||||
|
zecSend.amount.toZecString()
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Body(
|
||||||
|
text = zecSend.destination.abbreviated(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (zecSend.memo.value.isNotEmpty()) {
|
||||||
|
Body(
|
||||||
|
stringResource(
|
||||||
|
R.string.send_in_progress_memo_format,
|
||||||
|
zecSend.memo.value
|
||||||
|
),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(MINIMAL_WEIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
Body(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = dimens.spacingSmall)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(R.string.send_in_progress_wait),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SendSuccessful(
|
||||||
|
zecSend: ZecSend,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(
|
||||||
|
rememberScrollState()
|
||||||
|
)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
Header(
|
||||||
|
text = stringResource(R.string.send_successful_title),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(dimens.spacingDefault)
|
||||||
|
)
|
||||||
|
|
||||||
|
Body(
|
||||||
|
stringResource(
|
||||||
|
R.string.send_successful_amount_address_memo,
|
||||||
|
zecSend.amount.toZecString(),
|
||||||
|
zecSend.destination.abbreviated(),
|
||||||
|
zecSend.memo.valueOrEmptyChar()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(MINIMAL_WEIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier.padding(top = dimens.spacingSmall),
|
||||||
|
text = stringResource(R.string.send_successful_button),
|
||||||
|
onClick = onDone
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SendFailure(
|
||||||
|
zecSend: ZecSend,
|
||||||
|
onDone: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(
|
||||||
|
rememberScrollState()
|
||||||
|
)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
|
Header(
|
||||||
|
text = stringResource(R.string.send_failure_title),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(dimens.spacingDefault)
|
||||||
|
)
|
||||||
|
|
||||||
|
Body(
|
||||||
|
stringResource(
|
||||||
|
R.string.send_failure_amount_address_memo,
|
||||||
|
zecSend.amount.toZecString(),
|
||||||
|
zecSend.destination.abbreviated(),
|
||||||
|
zecSend.memo.valueOrEmptyChar()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(MINIMAL_WEIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier.padding(top = dimens.spacingSmall),
|
||||||
|
text = stringResource(R.string.send_failure_button),
|
||||||
|
onClick = onDone
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="fiat_currency_conversion_rate_unavailable">Unavailable</string>
|
<string name="fiat_currency_conversion_rate_unavailable">Unavailable</string>
|
||||||
|
<string name="empty_char">-</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -7,11 +7,23 @@
|
||||||
<string name="send_to">Who would you like to send ZEC to?</string>
|
<string name="send_to">Who would you like to send ZEC to?</string>
|
||||||
<string name="send_amount">How much?</string>
|
<string name="send_amount">How much?</string>
|
||||||
<string name="send_memo">Memo</string>
|
<string name="send_memo">Memo</string>
|
||||||
|
|
||||||
<string name="send_create">Send</string>
|
<string name="send_create">Send</string>
|
||||||
|
|
||||||
<string name="send_amount_and_address_format" formatted="true">Send <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g>?</string>
|
<string name="send_confirmation_amount_and_address_format" formatted="true">Send <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g>?</string>
|
||||||
<string name="send_abbreviated_address_format" formatted="true"><xliff:g id="first_five" example="zs1g7">%1$s</xliff:g>…<xliff:g id="last_five" example="mvyzg">%2$s</xliff:g></string>
|
<string name="send_confirmation_memo_format" formatted="true">Memo: <xliff:g id="memo" example="for Veronika">%1$s</xliff:g></string>
|
||||||
<string name="send_confirm">Press to send ZEC</string>
|
<string name="send_confirmation_abbreviated_address_format" formatted="true"><xliff:g id="first_five" example="zs1g7">%1$s</xliff:g>…<xliff:g id="last_five" example="mvyzg">%2$s</xliff:g></string>
|
||||||
|
<string name="send_confirmation_button">Press to send ZEC</string>
|
||||||
|
|
||||||
|
<string name="send_in_progress_amount_format" formatted="true">Sending <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to</string>
|
||||||
|
<string name="send_in_progress_memo_format" formatted="true">with a memo: <xliff:g id="memo" example="for Veronika">%1$s</xliff:g></string>
|
||||||
|
<string name="send_in_progress_wait">Please wait</string>
|
||||||
|
|
||||||
|
<string name="send_failure_title">Sending failure</string>
|
||||||
|
<string name="send_failure_amount_address_memo" formatted="true">Sending failed for: <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g> with a memo: <xliff:g id="memo" example="for Veronika">%3$s</xliff:g></string>
|
||||||
|
<string name="send_failure_button">Back</string>
|
||||||
|
|
||||||
|
<string name="send_successful_title">Sending successful</string>
|
||||||
|
<string name="send_successful_amount_address_memo" formatted="true">Sending succeeded for: <xliff:g id="amount" example="12.345">%1$s</xliff:g> ZEC to <xliff:g id="address" example="zs1g7cqw … mvyzgm">%2$s</xliff:g> with a memo: <xliff:g id="memo" example="for Veronika">%3$s</xliff:g></string>
|
||||||
|
<string name="send_successful_button">Close</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.test.performScrollTo
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.core.graphics.writeToTestStorage
|
import androidx.test.core.graphics.writeToTestStorage
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
import androidx.test.espresso.screenshot.captureToBitmap
|
import androidx.test.espresso.screenshot.captureToBitmap
|
||||||
|
@ -33,6 +34,7 @@ import androidx.test.filters.SdkSuppress
|
||||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||||
import cash.z.ecc.android.sdk.model.SeedPhrase
|
import cash.z.ecc.android.sdk.model.SeedPhrase
|
||||||
|
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||||
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
|
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
|
||||||
import co.electriccoin.zcash.configuration.model.map.StringConfiguration
|
import co.electriccoin.zcash.configuration.model.map.StringConfiguration
|
||||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||||
|
@ -532,25 +534,56 @@ private fun sendZecScreenshots(resContext: Context, tag: String, composeTestRule
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Screenshot: Empty form
|
||||||
ScreenshotTest.takeScreenshot(tag, "Send 1")
|
ScreenshotTest.takeScreenshot(tag, "Send 1")
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(resContext.getString(R.string.send_amount)).also {
|
composeTestRule.onNodeWithText(resContext.getString(R.string.send_amount)).also {
|
||||||
val separators = MonetarySeparators.current()
|
val separators = MonetarySeparators.current()
|
||||||
|
|
||||||
it.performTextInput("{${separators.decimal}}123")
|
it.performTextInput("0${separators.decimal}123")
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(resContext.getString(R.string.send_to)).also {
|
composeTestRule.onNodeWithText(resContext.getString(R.string.send_to)).also {
|
||||||
it.performTextInput(WalletAddressFixture.UNIFIED_ADDRESS_STRING)
|
it.performTextInput(WalletAddressFixture.UNIFIED_ADDRESS_STRING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText(resContext.getString(R.string.send_memo)).also {
|
||||||
|
it.performTextInput(MemoFixture.MEMO_STRING)
|
||||||
|
}
|
||||||
|
|
||||||
|
// To close soft keyboard to reveal the send button
|
||||||
|
Espresso.closeSoftKeyboard()
|
||||||
|
|
||||||
|
// Screenshot: Fulfilled form
|
||||||
|
ScreenshotTest.takeScreenshot(tag, "Send 2")
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(resContext.getString(R.string.send_create)).also {
|
composeTestRule.onNodeWithText(resContext.getString(R.string.send_create)).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.waitForIdle()
|
/*
|
||||||
|
TODO [#817]: Screenshot test on Send with pseudolocales problem
|
||||||
|
TODO [#817]: https://github.com/zcash/secant-android-wallet/issues/817
|
||||||
|
// Screenshot: Confirmation
|
||||||
|
ScreenshotTest.takeScreenshot(tag, "Send 3")
|
||||||
|
|
||||||
ScreenshotTest.takeScreenshot(tag, "Send 2")
|
composeTestRule.onNodeWithText(resContext.getString(R.string.send_confirmation_button)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screenshot: Sending
|
||||||
|
ScreenshotTest.takeScreenshot(tag, "Send 4")
|
||||||
|
|
||||||
|
// Note: this is potentially a long running waiting for the transaction submit result
|
||||||
|
// Remove this last section of taking screenshot if it turns out to be problematic
|
||||||
|
composeTestRule.waitUntil(DEFAULT_TIMEOUT_MILLISECONDS) {
|
||||||
|
composeTestRule.onAllNodesWithText(resContext.getString(R.string.send_in_progress_wait))
|
||||||
|
.fetchSemanticsNodes().isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screenshot: Result
|
||||||
|
ScreenshotTest.takeScreenshot(tag, "Send 5")
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun supportScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
|
private fun supportScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
|
||||||
|
|
Loading…
Reference in New Issue