diff --git a/app/src/main/java/com/kickstarter/viewmodels/projectpage/LatePledgeCheckoutViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/projectpage/LatePledgeCheckoutViewModel.kt index 22288d422a..38ea6593f8 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/projectpage/LatePledgeCheckoutViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/projectpage/LatePledgeCheckoutViewModel.kt @@ -1,5 +1,6 @@ package com.kickstarter.viewmodels.projectpage +import androidx.annotation.VisibleForTesting import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -27,6 +28,10 @@ import com.kickstarter.ui.data.PledgeData import com.stripe.android.Stripe import com.stripe.android.confirmPaymentIntent import com.stripe.android.model.ConfirmPaymentIntentParams +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -37,7 +42,11 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart @@ -55,6 +64,7 @@ data class LatePledgeCheckoutUIState( val isPledgeButtonEnabled: Boolean = true ) +@OptIn(ExperimentalCoroutinesApi::class) class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { private var pledgeData: PledgeData? = null @@ -84,6 +94,9 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { private var paymentIntent: String? = null private var pledgeButtonClickedJob: Job? = null + private var scope = viewModelScope + private var dispatcher = Dispatchers.IO + private var mutableOnPledgeSuccessAction = MutableSharedFlow() val onPledgeSuccess: SharedFlow get() = mutableOnPledgeSuccessAction @@ -122,27 +135,38 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { val paymentRequiresAction: SharedFlow get() = mutablePaymentRequiresAction.asSharedFlow() - init { - viewModelScope.launch { - environment.currentUserV2()?.observable()?.asFlow()?.distinctUntilChanged()?.map { - if (it.isPresent()) { - apolloClient.userPrivacy().asFlow() - .onStart { - emitCurrentState(isLoading = true) - }.map { userPrivacy -> - userEmail = userPrivacy.email - }.onCompletion { - emitCurrentState() - }.catch { - errorAction.invoke(null) - }.collect() + private val currentUser = requireNotNull( + environment.currentUserV2()?.loggedInUser()?.asFlow() + ) - refreshUserCards() - } - }?.catch { - errorAction.invoke(null) - }?.collect() + private suspend fun loadUserInfo() { + + val privacyAndCardsFlow = combine( + apolloClient.userPrivacy().asFlow(), + apolloClient.getStoredCards().asFlow() + ) { userPrivacy, cards -> + Pair(userPrivacy, cards) } + + currentUser + .filter { + it.isNotNull() + } + .distinctUntilChanged() + .flatMapConcat { + privacyAndCardsFlow + } + .onStart { + emitCurrentState(isLoading = true) + } + .catch { + errorAction.invoke(null) + } + .collectLatest { privacyAndCards -> + userEmail = privacyAndCards.first.email + storedCards = privacyAndCards.second + emitCurrentState() + } } fun getCheckoutData() = checkoutData @@ -154,7 +178,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { } fun onAddNewCardClicked(project: Project) { - viewModelScope.launch { + viewModelScope.launch(dispatcher) { apolloClient.createSetupIntent( project = project, ).asFlow().onStart { @@ -172,7 +196,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { } fun onNewCardSuccessfullyAdded() { - viewModelScope.launch { + viewModelScope.launch(dispatcher) { apolloClient.savePaymentMethod( SavePaymentMethodData( reusable = true, @@ -215,7 +239,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { if (pledgeButtonClickedJob?.isActive.isTrue()) return this.pledgeData?.let { - pledgeButtonClickedJob = viewModelScope.launch { + pledgeButtonClickedJob = viewModelScope.launch(dispatcher) { val project = it.projectData().project() buttonEnabled = false @@ -360,7 +384,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { } fun completeOnSessionCheckoutFor3DS() { - viewModelScope.launch { + scope.launch(dispatcher) { if (clientSecretFor3DSVerification.isNotEmpty() && selectedCardFor3DSVerification.isNotNull()) { apolloClient.completeOnSessionCheckout( checkoutId = checkoutId ?: "", @@ -395,7 +419,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { clientSecretFor3DSVerification = "" selectedCardFor3DSVerification = null - viewModelScope.launch { + scope.launch { emitCurrentState() } } @@ -453,7 +477,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { } fun loading() { - viewModelScope.launch { + scope.launch { emitCurrentState(isLoading = true) } } @@ -469,7 +493,7 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { .postCampaignPledgingEnabled() == true && projectData.project() .isInPostCampaignPledgingPhase() == true ) { - viewModelScope.launch { + scope.launch(dispatcher) { apolloClient.createCheckout( CreateCheckoutData( project = projectData.project(), @@ -501,16 +525,29 @@ class LatePledgeCheckoutViewModel(val environment: Environment) : ViewModel() { } } + /** + * For testing, by default run in + * scope: viewModelScope + * dispatcher: Dispatchers.IO + * + */ + @VisibleForTesting + internal fun provideScopeAndDispatcher(scope: CoroutineScope, dispatcher: CoroutineDispatcher) { + this.scope = scope + this.dispatcher = dispatcher + } + fun providePledgeData(pledgeData: PledgeData) { this.pledgeData = pledgeData this.checkoutData = createCheckoutData(pledgeData.shippingCostIfShipping(), pledgeData.pledgeAmountTotal(), pledgeData.bonusAmount()) - viewModelScope.launch { - selectedRewards.clear() - pledgeData.addOns()?.let { addOns -> - selectedRewards.add(pledgeData.reward()) - selectedRewards.addAll(addOns) - } + selectedRewards.clear() + pledgeData.addOns()?.let { addOns -> + selectedRewards.add(pledgeData.reward()) + selectedRewards.addAll(addOns) + } + scope.launch(dispatcher) { + loadUserInfo() createCheckout() } } diff --git a/app/src/test/java/com/kickstarter/viewmodels/LatePledgeCheckoutViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/LatePledgeCheckoutViewModelTest.kt index 19dae34428..9199c19b06 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/LatePledgeCheckoutViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/LatePledgeCheckoutViewModelTest.kt @@ -26,6 +26,7 @@ import com.kickstarter.ui.data.PledgeFlowContext import com.kickstarter.viewmodels.projectpage.LatePledgeCheckoutUIState import com.kickstarter.viewmodels.projectpage.LatePledgeCheckoutViewModel import io.reactivex.Observable +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -41,6 +42,7 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { viewModel = LatePledgeCheckoutViewModel.Factory(environment).create(LatePledgeCheckoutViewModel::class.java) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test loading() with emmit isLoading = true and isPledgeButtonEnabled= false`() = runTest { setUpEnvironment(environment()) @@ -63,68 +65,102 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_user_logged_in_then_email_is_provided`() = runTest { - val user = UserFactory.user() - val currentUserV2 = MockCurrentUserV2(initialUser = user) + fun `test when logged in user, UserPrivacy and StoreCards are retrieved`() = runTest { + val currentUserV2 = MockCurrentUserV2(UserFactory.user()) + val cardList = listOf(StoredCardFactory.visa()) val environment = environment().toBuilder() .apolloClientV2(object : MockApolloClientV2() { - override fun getStoredCards(): Observable> { - return Observable.empty() + override fun getStoredCards(): Observable> { // - mock the stored cards + return Observable.just(cardList) + } + + override fun userPrivacy(): Observable { // - mock the user email and name + return Observable.just( + UserPrivacy("Hola holita", "holaholi@gmail.com", true, true, true, true, "MXN") + ) } }) .currentUserV2(currentUserV2) .build() + val rw = RewardFactory.rewardWithShipping().toBuilder().latePledgeAmount(34.0).build() + val project = ProjectFactory.project().toBuilder() + .isInPostCampaignPledgingPhase(true) + .postCampaignPledgingEnabled(true) + .isBacking(false) + .rewards(listOf(rw)).build() + + val projectData = ProjectDataFactory.project(project = project) + val pledgeData = PledgeData.with(PledgeFlowContext.LATE_PLEDGES, projectData, rw) + setUpEnvironment(environment) + val dispatcher = UnconfinedTestDispatcher(testScheduler) val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.providePledgeData(pledgeData) viewModel.latePledgeCheckoutUIState.toList(state) } - assertEquals( - state.last(), - LatePledgeCheckoutUIState( - userEmail = "some@email.com" - ) - ) + advanceUntilIdle() + assertEquals(state.size, 3) + assertEquals(state.last().storeCards, cardList) + assertEquals(state.last().userEmail, "holaholi@gmail.com") } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_user_logged_in_then_cards_are_fetched`() = runTest { - val user = UserFactory.user() - val currentUserV2 = MockCurrentUserV2(initialUser = user) - val cardList = listOf(StoredCardFactory.visa()) + fun `test when not logged in user, no calls to UserPrivacy or StoreCards`() = runTest { + val cardList = listOf(StoredCardFactory.visa()) val environment = environment().toBuilder() .apolloClientV2(object : MockApolloClientV2() { - override fun getStoredCards(): Observable> { + override fun getStoredCards(): Observable> { // - mock the stored cards return Observable.just(cardList) } + + override fun userPrivacy(): Observable { // - mock the user email and name + return Observable.just( + UserPrivacy("Pika", "pika@gmail.com", true, true, true, true, "MXN") + ) + } }) - .currentUserV2(currentUserV2) .build() + val rw = RewardFactory.rewardWithShipping().toBuilder().latePledgeAmount(34.0).build() + val project = ProjectFactory.project().toBuilder() + .isInPostCampaignPledgingPhase(true) + .postCampaignPledgingEnabled(true) + .isBacking(false) + .rewards(listOf(rw)).build() + + val projectData = ProjectDataFactory.project(project = project) + val pledgeData = PledgeData.with(PledgeFlowContext.LATE_PLEDGES, projectData, rw) + setUpEnvironment(environment) + val dispatcher = UnconfinedTestDispatcher(testScheduler) val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.providePledgeData(pledgeData) viewModel.latePledgeCheckoutUIState.toList(state) } - assertEquals( - state.last(), - LatePledgeCheckoutUIState( - storeCards = cardList, - userEmail = "some@email.com" - ) - ) + advanceUntilIdle() + assertEquals(state.size, 2) + assertEquals(state.last().storeCards, emptyList()) + assertEquals(state.last().userEmail, "") } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_user_clicks_add_new_card_then_setup_intent_is_called`() = runTest { + fun test_when_user_clicks_add_new_card_then_setup_intent_is_called() = runTest { val user = UserFactory.user() val currentUserV2 = MockCurrentUserV2(initialUser = user) @@ -139,8 +175,11 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { setUpEnvironment(environment) + val dispatcher = UnconfinedTestDispatcher(testScheduler) val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) viewModel.clientSecretForNewPaymentMethod.toList(state) } @@ -152,8 +191,9 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_new_card_added_then_payment_methods_are_refreshed`() = runTest { + fun test_when_new_card_added_then_payment_methods_are_refreshed() = runTest { val user = UserFactory.user() val currentUserV2 = MockCurrentUserV2(initialUser = user) var cardList = mutableListOf(StoredCardFactory.visa()) @@ -163,42 +203,63 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { override fun getStoredCards(): Observable> { return Observable.just(cardList) } + + override fun userPrivacy(): Observable { + return Observable.just( + UserPrivacy( + "Some Name", + "some@email.com", + true, + true, + true, + true, + "USD" + ) + ) + } }) .currentUserV2(currentUserV2) .build() setUpEnvironment(environment) + val rw = RewardFactory.rewardWithShipping().toBuilder().latePledgeAmount(34.0).build() + val project = ProjectFactory.project().toBuilder() + .isInPostCampaignPledgingPhase(true) + .postCampaignPledgingEnabled(true) + .isBacking(false) + .rewards(listOf(rw)).build() + + val projectData = ProjectDataFactory.project(project = project) + val pledgeData = PledgeData.with(PledgeFlowContext.LATE_PLEDGES, projectData, rw) + + val dispatcher = UnconfinedTestDispatcher(testScheduler) val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.providePledgeData(pledgeData) viewModel.latePledgeCheckoutUIState.toList(state) } // Before List changes - assertEquals( - state.last(), - LatePledgeCheckoutUIState( - storeCards = cardList, - userEmail = "some@email.com" - ) - ) + assertEquals(state.last().storeCards, cardList) cardList = mutableListOf(StoredCardFactory.visa(), StoredCardFactory.discoverCard()) - viewModel.onNewCardSuccessfullyAdded() + backgroundScope.launch(dispatcher) { + viewModel.onNewCardSuccessfullyAdded() + } // After list is updated assertEquals( - state.last(), - LatePledgeCheckoutUIState( - storeCards = cardList, - userEmail = "some@email.com" - ) + state.last().storeCards, + cardList ) } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_new_card_adding_fails_then_state_emits`() = runTest { + fun test_when_new_card_adding_fails_then_state_emits() = runTest { val user = UserFactory.user() val currentUserV2 = MockCurrentUserV2(initialUser = user) val cardList = mutableListOf(StoredCardFactory.visa()) @@ -214,24 +275,35 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { setUpEnvironment(environment) + val rw = RewardFactory.rewardWithShipping().toBuilder().latePledgeAmount(34.0).build() + val project = ProjectFactory.project().toBuilder() + .isInPostCampaignPledgingPhase(true) + .postCampaignPledgingEnabled(true) + .isBacking(false) + .rewards(listOf(rw)).build() + + val projectData = ProjectDataFactory.project(project = project) + val pledgeData = PledgeData.with(PledgeFlowContext.LATE_PLEDGES, projectData, rw) + val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + val dispatcher = UnconfinedTestDispatcher(testScheduler) + + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.providePledgeData(pledgeData) + viewModel.onNewCardFailed() viewModel.latePledgeCheckoutUIState.toList(state) } - viewModel.onNewCardFailed() - assertEquals( - state.last(), - LatePledgeCheckoutUIState( - storeCards = cardList, - userEmail = "some@email.com" - ) + state.last().storeCards, + cardList ) - assertEquals(state.size, 2) + assertEquals(state.size, 3) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test when pledge_clicked_and_checkout_id_ and backingID not_provided then_error_action_is_called`() = runTest { @@ -294,9 +366,13 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { var errorActionCount = 0 val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + val dispatcher = UnconfinedTestDispatcher(testScheduler) + + backgroundScope.launch(dispatcher) { setUpEnvironment(environment) + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.provideErrorAction { errorActionCount++ } @@ -315,6 +391,7 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { assertEquals(errorActionCount, 1) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test when pledge_clicked for second time no new payment intent is created but using previous one`() = runTest { @@ -382,9 +459,11 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { var errorActionCount = 0 val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + val dispatcher = UnconfinedTestDispatcher(testScheduler) + backgroundScope.launch(dispatcher) { setUpEnvironment(environment) + viewModel.provideScopeAndDispatcher(this, dispatcher) viewModel.provideErrorAction { errorActionCount++ } @@ -400,8 +479,8 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { } advanceUntilIdle() - assertEquals(state.size, 5) - assertEquals(state[3].isPledgeButtonEnabled, false) + assertEquals(state.size, 4) + assertEquals(state[state.size - 2].isPledgeButtonEnabled, false) assertEquals(state.last().storeCards, cardList) assertEquals(state.last().userEmail, "some@email.com") assertEquals(state.last().isPledgeButtonEnabled, false) @@ -413,8 +492,9 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { assertEquals(paymentIntentCalled, 1) } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `test_when_pledge_clicked_and_checkout_id_provided_then_checkout_continues`() = runTest { + fun test_when_pledge_clicked_and_checkout_id_provided_then_checkout_continues() = runTest { val user = UserFactory.user() val currentUserV2 = MockCurrentUserV2(initialUser = user) val cardList = mutableListOf(StoredCardFactory.visa()) @@ -479,8 +559,10 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { var errorActionCount = 0 val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + val dispatcher = UnconfinedTestDispatcher(testScheduler) + backgroundScope.launch(dispatcher) { setUpEnvironment(environment) + viewModel.provideScopeAndDispatcher(this, dispatcher) viewModel.provideErrorAction { errorActionCount++ @@ -504,6 +586,7 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { assertEquals(paymentIntentCalled, 1) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test_when_complete3DSCheckout_called_with_no_values_then_errors`() = runTest { val user = UserFactory.user() @@ -530,16 +613,20 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { var errorActionCount = 0 - viewModel.provideErrorAction { - errorActionCount++ + val dispatcher = UnconfinedTestDispatcher(testScheduler) + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.provideErrorAction { + errorActionCount++ + } + viewModel.completeOnSessionCheckoutFor3DS() } - viewModel.completeOnSessionCheckoutFor3DS() - assertEquals(errorActionCount, 1) assertEquals(completeOnSessionCheckoutCalled, 0) } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test send PageViewed event`() = runTest { setUpEnvironment(environment()) @@ -590,6 +677,7 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { } } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test send CTAClicked event`() = runTest { setUpEnvironment(environment()) @@ -639,8 +727,9 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { } } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `Test VM init state when user and stored cards requests succeed will generate state with saved cards and user email`() = runTest { + fun `Test VM init state will generate empty State`() = runTest { val discover = StoredCardFactory.discoverCard() val visa = StoredCardFactory.visa() val cardsList = listOf(visa, discover) @@ -664,21 +753,21 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { ) val state = mutableListOf() - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + val dispatcher = UnconfinedTestDispatcher(testScheduler) + backgroundScope.launch(dispatcher) { viewModel.latePledgeCheckoutUIState.toList(state) } - assertEquals(state.size, 2) - assertEquals(state.last().userEmail, "hola@gmail.com") - assertEquals(state.last().storeCards, cardsList) - assertEquals(state.last().storeCards.first(), cardsList.first()) - assertEquals(state.last().storeCards.last(), cardsList.last()) + assertEquals(state.size, 1) + assertEquals(state.last().userEmail, "") + assertEquals(state.last().storeCards, emptyList()) assertEquals(state.last().isLoading, false) assertEquals(state.last().isPledgeButtonEnabled, true) } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `Test VM error init state when user or stored cards requests fail will generate state without saved cards or user email`() = runTest { + fun `Test VM error when UserPrivacy or StoreCards requests fail will generate state without saved cards or user email`() = runTest { val currentUser = MockCurrentUserV2(UserFactory.user()) setUpEnvironment( @@ -696,14 +785,33 @@ class LatePledgeCheckoutViewModelTest : KSRobolectricTestCase() { }).build() ) + val rw = RewardFactory.rewardWithShipping().toBuilder().latePledgeAmount(34.0).build() + val project = ProjectFactory.project().toBuilder() + .isInPostCampaignPledgingPhase(true) + .postCampaignPledgingEnabled(true) + .isBacking(false) + .rewards(listOf(rw)).build() + + val projectData = ProjectDataFactory.project(project = project) + val pledgeData = PledgeData.with(PledgeFlowContext.LATE_PLEDGES, projectData, rw) + + var errorActionCount = 0 + val state = mutableListOf() + val dispatcher = UnconfinedTestDispatcher(testScheduler) - backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { + backgroundScope.launch(dispatcher) { + viewModel.provideScopeAndDispatcher(this, dispatcher) + viewModel.provideErrorAction { + errorActionCount++ + } + viewModel.providePledgeData(pledgeData) viewModel.latePledgeCheckoutUIState.toList(state) } - assertEquals(state.size, 1) + assertEquals(state.size, 3) assertEquals(state.last().userEmail, "") assertEquals(state.last().storeCards, emptyList()) + assertEquals(errorActionCount, 1) } }