Updated on 2026-08-14
This commit is contained in:
parent
c8c4f67018
commit
dfaf4e4e77
16 changed files with 511 additions and 108 deletions
|
|
@ -35,6 +35,7 @@ dependencies {
|
|||
|
||||
/* Tangem libraries */
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/* Reown - WalletConnect */
|
||||
implementation(deps.reownCore) {
|
||||
|
|
@ -47,4 +48,10 @@ dependencies {
|
|||
/* Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/* Tests */
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.turbine)
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import com.tangem.data.walletconnect.pair.DefaultWcPairUseCase
|
|||
import com.tangem.data.walletconnect.request.DefaultWcRequestService
|
||||
import com.tangem.data.walletconnect.request.WcMethodHandler
|
||||
import com.tangem.data.walletconnect.respond.DefaultWcRespondService
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
|
|
@ -25,7 +26,6 @@ import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsReposit
|
|||
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.request.WcRequestService
|
||||
import com.tangem.domain.walletconnect.respond.WcRespondService
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -131,9 +131,8 @@ internal object WalletConnectDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcEthNetwork(@SdkMoshi moshi: Moshi, respondService: WcRespondService): WcEthNetwork = WcEthNetwork(
|
||||
fun wcEthNetwork(@SdkMoshi moshi: Moshi): WcEthNetwork = WcEthNetwork(
|
||||
moshi = moshi,
|
||||
respondService = respondService,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -8,10 +8,8 @@ import com.tangem.data.walletconnect.request.WcMethodHandler
|
|||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
import com.tangem.domain.walletconnect.model.WcRequest
|
||||
import com.tangem.domain.walletconnect.respond.WcRespondService
|
||||
import com.tangem.domain.walletconnect.usecase.WcUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider
|
||||
import com.tangem.domain.walletconnect.usecase.ethereum.EthPersonalSignUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod.SignMessage
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
|
@ -19,7 +17,6 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||
|
||||
internal class WcEthNetwork(
|
||||
private val moshi: Moshi,
|
||||
private val respondService: WcRespondService,
|
||||
) : WcMethodHandler<WcEthMethod>, WcUseCasesFlowProvider, WcNamespaceConverter {
|
||||
|
||||
private val _useCases: Channel<WcUseCase> = Channel(Channel.BUFFERED)
|
||||
|
|
@ -46,7 +43,7 @@ internal class WcEthNetwork(
|
|||
override fun handle(wcRequest: WcRequest<WcMethod>) {
|
||||
wcRequest as WcRequest<WcEthMethod>
|
||||
val useCase = when (wcRequest.method) {
|
||||
is SignMessage -> EthPersonalSignUseCase(wcRequest as WcRequest<SignMessage>, respondService)
|
||||
is SignMessage -> TODO()
|
||||
}
|
||||
_useCases.trySend(useCase)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.data.walletconnect.request
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionRequestConverter
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
import com.tangem.domain.walletconnect.model.WcRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.request.WcRequestService
|
||||
import com.tangem.domain.walletconnect.respond.WcRespondService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import arrow.core.right
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.respond.WcRespondService
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
|
|
@ -50,4 +49,19 @@ internal class DefaultWcRespondService : WcRespondService {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String) {
|
||||
WalletKit.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = request.topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError(
|
||||
id = request.request.id,
|
||||
code = 0,
|
||||
message = message,
|
||||
),
|
||||
),
|
||||
onSuccess = {},
|
||||
onError = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.data.walletconnect.respond
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
||||
interface WcRespondService {
|
||||
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit>
|
||||
suspend fun rejectRequest(request: WcSdkSessionRequest, message: String = ""): Either<Throwable, Unit>
|
||||
fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String = "")
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.tangem.data.walletconnect.sign
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
|
||||
internal abstract class BaseWcSignUseCase<MiddleAction, SignModel> :
|
||||
WcMethodUseCase,
|
||||
WcSignUseCase.FinalAction,
|
||||
FinalActionCollector<SignModel>,
|
||||
MiddleActionCollector<MiddleAction, SignModel> {
|
||||
|
||||
abstract val respondService: WcRespondService
|
||||
|
||||
abstract val context: WcMethodUseCaseContext
|
||||
override val session: WcSession get() = context.session
|
||||
override val rawSdkRequest: WcSdkSessionRequest get() = context.rawSdkRequest
|
||||
|
||||
protected val delegate by lazy {
|
||||
WcSignUseCaseDelegate(
|
||||
finalActionCollector = this,
|
||||
middleActionCollector = this,
|
||||
)
|
||||
}
|
||||
|
||||
override val onCancel: suspend (currentState: WcSignState<SignModel>) -> Unit = {
|
||||
defaultReject()
|
||||
}
|
||||
|
||||
override fun sign() = delegate.sign()
|
||||
override fun cancel() = delegate.cancel()
|
||||
protected fun middleAction(action: MiddleAction) = delegate.middleAction(action)
|
||||
|
||||
protected fun defaultReject() {
|
||||
respondService.rejectRequestNonBlock(rawSdkRequest)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface MiddleActionCollector<MiddleAction, SignModel> {
|
||||
|
||||
val onMiddleAction: OnMiddle<MiddleAction, SignModel> get() = { _, _ -> }
|
||||
}
|
||||
|
||||
internal interface FinalActionCollector<SignModel> {
|
||||
|
||||
val onSign: OnSign<SignModel> get() = {}
|
||||
|
||||
val onCancel: OnCancel<SignModel> get() = {}
|
||||
}
|
||||
|
||||
internal class WcMethodUseCaseContext(
|
||||
val session: WcSession,
|
||||
val rawSdkRequest: WcSdkSessionRequest,
|
||||
)
|
||||
|
||||
internal typealias OnSign<SignModel> =
|
||||
suspend FlowCollector<WcSignState<SignModel>>.(state: WcSignState<SignModel>) -> Unit
|
||||
internal typealias OnCancel<SignModel> =
|
||||
suspend (currentState: WcSignState<SignModel>) -> Unit
|
||||
internal typealias OnMiddle<MiddleAction, SignModel> =
|
||||
suspend FlowCollector<SignModel>.(currentState: WcSignState<SignModel>, middleAction: MiddleAction) -> Unit
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.data.walletconnect.sign
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignStep
|
||||
|
||||
object SignStateConverter {
|
||||
|
||||
internal fun <M> preSign(signModel: M) = WcSignState(signModel, WcSignStep.PreSign)
|
||||
internal fun <M> signing(signModel: M) = WcSignState(signModel, WcSignStep.Signing)
|
||||
internal fun <M> result(result: Either<Throwable, Unit>, signModel: M) =
|
||||
WcSignState(signModel, WcSignStep.Result(result))
|
||||
|
||||
internal fun <M> WcSignState<M>.toPreSign(signModel: M = this.signModel) = copy(
|
||||
signModel = signModel,
|
||||
domainStep = WcSignStep.PreSign,
|
||||
)
|
||||
|
||||
internal fun <M> WcSignState<M>.toSigning(signModel: M = this.signModel) = copy(
|
||||
domainStep = WcSignStep.Signing,
|
||||
signModel = signModel,
|
||||
)
|
||||
|
||||
internal fun <M> WcSignState<M>.toResult(result: Either<Throwable, Unit>, signModel: M = this.signModel) = copy(
|
||||
domainStep = WcSignStep.Result(result),
|
||||
signModel = signModel,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.data.walletconnect.sign
|
||||
|
||||
import arrow.core.left
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toPreSign
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignStep
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
|
||||
private val finalActionCollector: FinalActionCollector<SignModel>,
|
||||
private val middleActionCollector: MiddleActionCollector<MiddleAction, SignModel>,
|
||||
) : FinalActionCollector<SignModel> by finalActionCollector,
|
||||
MiddleActionCollector<MiddleAction, SignModel> by middleActionCollector {
|
||||
|
||||
private val middleActionsChannel = Channel<MiddleAction>()
|
||||
private val finalActionsChannel = Channel<Action>()
|
||||
|
||||
fun cancel() {
|
||||
finalActionsChannel.trySend(Action.Cancel)
|
||||
}
|
||||
|
||||
fun sign() {
|
||||
finalActionsChannel.trySend(Action.Sign)
|
||||
}
|
||||
|
||||
fun middleAction(action: MiddleAction) {
|
||||
middleActionsChannel.trySend(action)
|
||||
}
|
||||
|
||||
operator fun invoke(initModel: SignModel) = channelFlow {
|
||||
val state = MutableStateFlow(WcSignState(initModel, WcSignStep.PreSign))
|
||||
|
||||
state
|
||||
.onEach { newState -> channel.send(newState) }
|
||||
.launchIn(this)
|
||||
|
||||
fun listenMiddle() = middleActionsChannel.receiveAsFlow()
|
||||
.buffer()
|
||||
.transform { middleActions -> this.onMiddleAction(state.value, middleActions) }
|
||||
.onEach { updatedModel -> state.update { it.toPreSign(updatedModel) } }
|
||||
.launchIn(this)
|
||||
|
||||
var listenMiddleJob: Job = listenMiddle()
|
||||
|
||||
fun signFlow() = flow { onSign(state.updateAndGet { it.toSigning() }) }
|
||||
.onEach { newState -> state.update { newState } }
|
||||
.catch { exception ->
|
||||
val errorResult = state.value.toResult(exception.left())
|
||||
state.update { errorResult }
|
||||
}
|
||||
|
||||
var signJob: Job? = null
|
||||
|
||||
finalActionsChannel.receiveAsFlow()
|
||||
.transformLatest<Action, Unit> { finalAction ->
|
||||
when (finalAction) {
|
||||
Action.Cancel -> {
|
||||
onCancel.invoke(state.value)
|
||||
channel.close()
|
||||
}
|
||||
Action.Sign -> {
|
||||
val isSigningNow = signJob?.isActive == true
|
||||
if (isSigningNow) return@transformLatest
|
||||
listenMiddleJob.cancel()
|
||||
signJob = launch {
|
||||
signFlow().collect()
|
||||
listenMiddleJob = listenMiddle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
|
||||
/**
|
||||
* keep flow running to attempt re-signing after an error
|
||||
* or do something after a successful sign
|
||||
*/
|
||||
awaitClose()
|
||||
}
|
||||
|
||||
sealed interface Action {
|
||||
data object Cancel : Action
|
||||
data object Sign : Action
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
package com.tangem.domain.walletconnect
|
||||
|
||||
import app.cash.turbine.test
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.data.walletconnect.sign.FinalActionCollector
|
||||
import com.tangem.data.walletconnect.sign.MiddleActionCollector
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
|
||||
import com.tangem.data.walletconnect.sign.WcSignUseCaseDelegate
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignStep
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
internal class WcSignUseCaseDelegateTest {
|
||||
|
||||
private val middleActionCollector = mockk<MiddleActionCollector<TestMiddleAction, TestSignModel>>()
|
||||
private val finalActionCollector = mockk<FinalActionCollector<TestSignModel>>()
|
||||
private val useCase = WcSignUseCaseDelegate(
|
||||
finalActionCollector = finalActionCollector,
|
||||
middleActionCollector = middleActionCollector,
|
||||
)
|
||||
private val initSignModel = TestSignModel()
|
||||
|
||||
private val initState = WcSignState(initSignModel, WcSignStep.PreSign)
|
||||
private val signing = initState.toSigning()
|
||||
private val result = signing.toResult(Unit.right())
|
||||
private val testException = RuntimeException("test")
|
||||
|
||||
private val successSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
|
||||
currentState: WcSignState<TestSignModel>,
|
||||
) -> Unit = { state ->
|
||||
delay(2)
|
||||
emit(state.toResult(Unit.right()))
|
||||
}
|
||||
|
||||
private val failedSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
|
||||
currentState: WcSignState<TestSignModel>,
|
||||
) -> Unit
|
||||
get() = { state ->
|
||||
delay(2)
|
||||
emit(state.toResult(testException.left()))
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
every { middleActionCollector.onMiddleAction } returns { _, _ -> }
|
||||
every { finalActionCollector.onSign } returns { }
|
||||
every { finalActionCollector.onCancel } returns { }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke and keep flow running`() = runTest {
|
||||
every { finalActionCollector.onSign } returns successSign
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `success sign, keep flow running`() = runTest {
|
||||
every { finalActionCollector.onSign } returns successSign
|
||||
|
||||
useCase.invoke(initModel = initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
useCase.sign()
|
||||
assertEquals(signing, awaitItem())
|
||||
assertEquals(result, awaitItem())
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed sign, keep flow running`() = runTest {
|
||||
val failedResult = signing.toResult(testException.left())
|
||||
every { finalActionCollector.onSign } returns failedSign
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
useCase.sign()
|
||||
assertEquals(signing, awaitItem())
|
||||
assertEquals(failedResult, awaitItem())
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed sign and catch unknown exception`() = runTest {
|
||||
val exception = RuntimeException("asd")
|
||||
val expectedErrorState = signing.toResult(exception.left())
|
||||
every { finalActionCollector.onSign } returns {
|
||||
delay(2)
|
||||
throw exception
|
||||
}
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
useCase.sign()
|
||||
assertEquals(signing, awaitItem())
|
||||
assertEquals(expectedErrorState, awaitItem())
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `interrupt signing and complete flow on cancel call`() = runTest {
|
||||
every { finalActionCollector.onSign } returns {
|
||||
delay(5)
|
||||
emit(result)
|
||||
}
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
useCase.sign()
|
||||
assertEquals(signing, awaitItem())
|
||||
delay(2)
|
||||
useCase.cancel()
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ignore multi time sign call till signed`() = runTest {
|
||||
var count = 0
|
||||
val startLoading = WcSignState(TestSignModel("startLoading 1"), WcSignStep.Signing)
|
||||
val startLoading2 = WcSignState(TestSignModel("startLoading 2"), WcSignStep.Signing)
|
||||
val expectedSignResult = result
|
||||
|
||||
every { finalActionCollector.onSign } returns {
|
||||
// should emit single time in this test
|
||||
emit(if (count % 2 == 0) startLoading else startLoading2)
|
||||
count = count.inc()
|
||||
delay(10)
|
||||
emit(expectedSignResult)
|
||||
}
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
useCase.sign()
|
||||
delay(2)
|
||||
assertEquals(startLoading, expectMostRecentItem())
|
||||
|
||||
// should ignore
|
||||
useCase.sign()
|
||||
delay(2)
|
||||
expectNoEvents()
|
||||
|
||||
// should ignore
|
||||
useCase.sign()
|
||||
expectNoEvents()
|
||||
|
||||
delay(8)
|
||||
assertEquals(expectedSignResult, expectMostRecentItem())
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ignore middle actions while signing, on failed collect middle actions again`() = runTest {
|
||||
val firstTextMode = TestSignModel(TestMiddleAction.One().newTestStr)
|
||||
val firstMiddleUpdate = WcSignState(
|
||||
signModel = firstTextMode,
|
||||
domainStep = WcSignStep.PreSign,
|
||||
)
|
||||
val startLoading = firstMiddleUpdate.toSigning()
|
||||
val failedSign = startLoading.toResult(testException.left())
|
||||
val thirdMiddleUpdate = WcSignState(
|
||||
signModel = TestSignModel(TestMiddleAction.Three().newTestStr),
|
||||
domainStep = WcSignStep.PreSign,
|
||||
)
|
||||
|
||||
every { finalActionCollector.onSign } returns {
|
||||
delay(6)
|
||||
emit(failedSign)
|
||||
}
|
||||
|
||||
every { middleActionCollector.onMiddleAction } returns { currentState, middleAction ->
|
||||
emit(currentState.signModel.copy(testStr = middleAction.newTestStr))
|
||||
}
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
delay(2)
|
||||
useCase.middleAction(TestMiddleAction.One())
|
||||
assertEquals(firstMiddleUpdate, expectMostRecentItem())
|
||||
|
||||
useCase.sign()
|
||||
assertEquals(startLoading, awaitItem())
|
||||
|
||||
// should ignore
|
||||
delay(2)
|
||||
useCase.middleAction(TestMiddleAction.Two())
|
||||
|
||||
delay(3)
|
||||
assertEquals(failedSign, awaitItem())
|
||||
|
||||
// continue listen
|
||||
delay(2)
|
||||
useCase.middleAction(TestMiddleAction.Three())
|
||||
assertEquals(thirdMiddleUpdate, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buffered middle actions and drop on sign call`() = runTest {
|
||||
val firstTextMode = TestSignModel(TestMiddleAction.One().newTestStr)
|
||||
val expectedFirst = initState.copy(signModel = firstTextMode)
|
||||
val expectedSecond = initState.copy(signModel = TestSignModel(TestMiddleAction.Two().newTestStr))
|
||||
|
||||
every { finalActionCollector.onSign } returns successSign
|
||||
every { middleActionCollector.onMiddleAction } returns { currentState, middleAction ->
|
||||
emit(currentState.signModel.copy(testStr = middleAction.newTestStr))
|
||||
delay(4)
|
||||
}
|
||||
|
||||
useCase.invoke(initSignModel).test {
|
||||
assertEquals(initState, awaitItem())
|
||||
useCase.middleAction(TestMiddleAction.One())
|
||||
useCase.middleAction(TestMiddleAction.Two())
|
||||
// must be dropped
|
||||
useCase.middleAction(TestMiddleAction.Three())
|
||||
|
||||
// 0 - 4 -> "one" is emitted
|
||||
// 4 - 8 -> "two" is emitted
|
||||
// 8 - 12 -> "Signing" is emitted, "three" ignored
|
||||
delay(2)
|
||||
assertEquals(expectedFirst, awaitItem())
|
||||
delay(4)
|
||||
assertEquals(expectedSecond, awaitItem())
|
||||
|
||||
useCase.sign()
|
||||
delay(4)
|
||||
assertEquals(expectedSecond.toSigning(), awaitItem())
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
internal data class TestSignModel(val testStr: String = "testStr")
|
||||
|
||||
internal sealed interface TestMiddleAction {
|
||||
val newTestStr: String
|
||||
|
||||
data class One(override val newTestStr: String = "Middle Action One") : TestMiddleAction
|
||||
data class Two(override val newTestStr: String = "Middle Action Two") : TestMiddleAction
|
||||
data class Three(override val newTestStr: String = "Middle Action Three") : TestMiddleAction
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue