Added metrics around sending funds.
This commit is contained in:
parent
f8603d424a
commit
4283a771f6
|
@ -19,10 +19,20 @@ object Report {
|
|||
SEED_PHRASE_LOADED("metric.seedphrase.loaded", "seed phrase loaded"),
|
||||
WALLET_CREATED("metric.wallet.created", "wallet created"),
|
||||
WALLET_IMPORTED("metric.wallet.imported", "wallet imported"),
|
||||
ACCOUNT_CREATED("metric.account.created", "account created")
|
||||
ACCOUNT_CREATED("metric.account.created", "account created"),
|
||||
|
||||
// Transactions
|
||||
TRANSACTION_INITIALIZED("metric.tx.initialized", "transaction initialized"),
|
||||
TRANSACTION_CREATED("metric.tx.created", "transaction created successfully"),
|
||||
TRANSACTION_SUBMITTED("metric.tx.submitted", "transaction submitted successfully"),
|
||||
TRANSACTION_MINED("metric.tx.mined", "transaction mined")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metric with a start time of ZcashWalletApp.creationTime and an end time of when this
|
||||
* instance was created. This can then be passed to [Feedback.report].
|
||||
*/
|
||||
class LaunchMetric private constructor(private val metric: Feedback.TimeMetric) :
|
||||
Feedback.Metric by metric {
|
||||
constructor() : this(
|
||||
|
|
|
@ -1,23 +1,36 @@
|
|||
package cash.z.ecc.android.ui.send
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.android.feedback.Feedback
|
||||
import cash.z.ecc.android.feedback.Feedback.Keyed
|
||||
import cash.z.ecc.android.feedback.Feedback.TimeMetric
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.MetricType
|
||||
import cash.z.ecc.android.feedback.Report.MetricType.*
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.entity.PendingTransaction
|
||||
import cash.z.wallet.sdk.annotation.OpenForTesting
|
||||
import cash.z.wallet.sdk.entity.*
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk
|
||||
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class SendViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
private val metrics = mutableMapOf<String, TimeMetric>()
|
||||
|
||||
@Inject
|
||||
lateinit var lockBox: LockBox
|
||||
|
||||
|
@ -27,6 +40,23 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
@Inject
|
||||
lateinit var initializer: Initializer
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
var fromAddress: String = ""
|
||||
var toAddress: String = ""
|
||||
var memo: String = ""
|
||||
var zatoshiAmount: Long = -1L
|
||||
var includeFromAddress: Boolean = false
|
||||
set(value) {
|
||||
require(!value || (value && !fromAddress.isNullOrEmpty())) {
|
||||
"Error: from address was empty while attempting to include it in the memo. Verify" +
|
||||
" that initFromAddress() has previously been called on this viewmodel."
|
||||
}
|
||||
field = value
|
||||
}
|
||||
val isShielded get() = toAddress.startsWith("z")
|
||||
|
||||
fun send(): Flow<PendingTransaction> {
|
||||
val memoToSend = if (includeFromAddress) "$memo\nsent from\n$fromAddress" else memo
|
||||
val keys = initializer.deriveSpendingKeys(
|
||||
|
@ -52,10 +82,10 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
emit("Please enter a valid address")
|
||||
}
|
||||
zatoshiAmount < ZcashSdk.MINERS_FEE_ZATOSHI -> {
|
||||
emit("Please enter at least 0.0001")
|
||||
emit("Too little! Please enter at least 0.0001")
|
||||
}
|
||||
maxZatoshi != null && zatoshiAmount > maxZatoshi -> {
|
||||
emit( "Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)}")
|
||||
emit( "Too much! Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)}")
|
||||
}
|
||||
else -> emit(null)
|
||||
}
|
||||
|
@ -76,17 +106,73 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
includeFromAddress = false
|
||||
}
|
||||
|
||||
var fromAddress: String = ""
|
||||
var toAddress: String = ""
|
||||
var memo: String = ""
|
||||
var zatoshiAmount: Long = -1L
|
||||
var includeFromAddress: Boolean = false
|
||||
set(value) {
|
||||
require(!value || (value && !fromAddress.isNullOrEmpty())) {
|
||||
"Error: from address was empty while attempting to include it in the memo. Verify" +
|
||||
" that initFromAddress() has previously been called on this viewmodel."
|
||||
fun updateMetrics(tx: PendingTransaction) {
|
||||
try {
|
||||
when {
|
||||
tx.isMined() -> TRANSACTION_SUBMITTED to TRANSACTION_MINED by tx.id
|
||||
tx.isSubmitSuccess() -> TRANSACTION_CREATED to TRANSACTION_SUBMITTED by tx.id
|
||||
tx.isCreated() -> TRANSACTION_INITIALIZED to TRANSACTION_CREATED by tx.id
|
||||
tx.isCreating() -> +TRANSACTION_INITIALIZED by tx.id
|
||||
else -> null
|
||||
}?.let { metricId ->
|
||||
report(metricId)
|
||||
}
|
||||
field = value
|
||||
} catch (t: Throwable) {
|
||||
Crashlytics.logException(RuntimeException("Error while updating Metrics", t))
|
||||
}
|
||||
val isShielded get() = toAddress.startsWith("z")
|
||||
}
|
||||
}
|
||||
|
||||
fun report(metricId: String?) {
|
||||
metrics[metricId]?.let { metric ->
|
||||
metric.takeUnless { (it.elapsedTime ?: 0) <= 0L }?.let {
|
||||
viewModelScope.launch {
|
||||
withContext(IO) {
|
||||
feedback.report(metric)
|
||||
|
||||
// does this metric complete another metric?
|
||||
metricId!!.toRelatedMetricId().let { relatedId ->
|
||||
metrics[relatedId]?.let { relatedMetric ->
|
||||
// then remove the related metric, itself. And the relation.
|
||||
metrics.remove(relatedMetric.toMetricIdFor(metricId!!.toTxId()))
|
||||
metrics.remove(relatedId)
|
||||
}
|
||||
}
|
||||
|
||||
// remove all top-level metrics
|
||||
if (metric.key == Report.MetricType.TRANSACTION_MINED.key) metrics.remove(metricId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun MetricType.unaryPlus(): TimeMetric = TimeMetric(key, description).markTime()
|
||||
private infix fun TimeMetric.by(txId: Long) = this.toMetricIdFor(txId).also { metrics[it] = this }
|
||||
private infix fun Pair<MetricType, MetricType>.by(txId: Long): String? {
|
||||
val startMetric = first.toMetricIdFor(txId).let { metricId ->
|
||||
metrics[metricId].also { if (it == null) println("Warning no start metric for id: $metricId") }
|
||||
}
|
||||
return startMetric?.endTime?.let { startMetricEndTime ->
|
||||
TimeMetric(second.key, second.description, mutableListOf(startMetricEndTime))
|
||||
.markTime().let { endMetric ->
|
||||
endMetric.toMetricIdFor(txId).also { metricId ->
|
||||
metrics[metricId] = endMetric
|
||||
metrics[metricId.toRelatedMetricId()] = startMetric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Keyed<String>.toMetricIdFor(id: Long): String = "$id.$key"
|
||||
private fun String.toRelatedMetricId(): String = "$this.related"
|
||||
private fun String.toTxId(): Long = split('.').first().toLong()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
android:width="108dp">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
|
@ -1,171 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
android:width="108dp">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,111 @@
|
|||
package cash.z.ecc.android
|
||||
|
||||
import cash.z.ecc.android.feedback.Feedback
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.wallet.sdk.entity.*
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Spy
|
||||
|
||||
class SendViewModelTest {
|
||||
|
||||
@Mock lateinit var creatingTx: PendingTransaction
|
||||
@Mock lateinit var createdTx: PendingTransaction
|
||||
@Mock lateinit var submittedTx: PendingTransaction
|
||||
@Mock lateinit var minedTx: PendingTransaction
|
||||
|
||||
@Mock
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
@Spy
|
||||
lateinit var sendViewModel: SendViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
Dispatchers.setMain(newSingleThreadContext("Main thread"))
|
||||
|
||||
whenever(creatingTx.id).thenReturn(7)
|
||||
whenever(creatingTx.submitAttempts).thenReturn(0)
|
||||
|
||||
whenever(createdTx.id).thenReturn(7)
|
||||
whenever(createdTx.raw).thenReturn(byteArrayOf(0x1))
|
||||
|
||||
whenever(submittedTx.id).thenReturn(7)
|
||||
whenever(submittedTx.raw).thenReturn(byteArrayOf(0x1))
|
||||
whenever(submittedTx.submitAttempts).thenReturn(1)
|
||||
|
||||
whenever(minedTx.id).thenReturn(7)
|
||||
whenever(minedTx.raw).thenReturn(byteArrayOf(0x1))
|
||||
whenever(minedTx.submitAttempts).thenReturn(1)
|
||||
whenever(minedTx.minedHeight).thenReturn(500_001)
|
||||
|
||||
sendViewModel.feedback = feedback
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateMetrics_creating() {
|
||||
// doNothing().whenever(sendViewModel).report(any())
|
||||
|
||||
assertEquals(true, creatingTx.isCreating())
|
||||
sendViewModel.updateMetrics(creatingTx)
|
||||
|
||||
verify(sendViewModel).report("7.metric.tx.initialized")
|
||||
assertEquals(1, sendViewModel.metrics.size)
|
||||
verifyZeroInteractions(feedback)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateMetrics_created() {
|
||||
assertEquals(false, createdTx.isCreating())
|
||||
assertEquals(true, createdTx.isCreated())
|
||||
sendViewModel.updateMetrics(creatingTx)
|
||||
sendViewModel.updateMetrics(createdTx)
|
||||
Thread.sleep(100)
|
||||
println(sendViewModel.metrics)
|
||||
|
||||
verify(sendViewModel).report("7.metric.tx.created")
|
||||
assertEquals(1, sendViewModel.metrics.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateMetrics_submitted() {
|
||||
assertEquals(false, submittedTx.isCreating())
|
||||
assertEquals(false, submittedTx.isCreated())
|
||||
assertEquals(true, submittedTx.isSubmitSuccess())
|
||||
sendViewModel.updateMetrics(creatingTx)
|
||||
sendViewModel.updateMetrics(createdTx)
|
||||
sendViewModel.updateMetrics(submittedTx)
|
||||
assertEquals(5, sendViewModel.metrics.size)
|
||||
|
||||
Thread.sleep(100)
|
||||
assertEquals(1, sendViewModel.metrics.size)
|
||||
|
||||
verify(feedback).report(sendViewModel.metrics.values.first())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testUpdateMetrics_mined() {
|
||||
assertEquals(true, minedTx.isMined())
|
||||
assertEquals(true, minedTx.isSubmitSuccess())
|
||||
sendViewModel.updateMetrics(creatingTx)
|
||||
sendViewModel.updateMetrics(createdTx)
|
||||
sendViewModel.updateMetrics(submittedTx)
|
||||
sendViewModel.updateMetrics(minedTx)
|
||||
assertEquals(7, sendViewModel.metrics.size)
|
||||
|
||||
Thread.sleep(100)
|
||||
assertEquals(0, sendViewModel.metrics.size)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mock-maker-inline
|
Loading…
Reference in New Issue