From e41e74b4316c31b3a083c60a008134f7fa24afe1 Mon Sep 17 00:00:00 2001 From: mozhganpeivandiansharbaf Date: Thu, 27 Nov 2025 16:43:12 +0100 Subject: [PATCH] feat(capture-sdk): add UI tests for AnalysisFragment and enhance presenter functionality PP-1463 --- .../AnalysisScreenPresenterExtension.kt | 4 +- .../capture/analysis/AnalysisFragmentTest.kt | 59 +++++++++++++++ .../analysis/AnalysisScreenPresenterTest.kt | 71 +++++++++++++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt index fcdd354aa..118599757 100644 --- a/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt +++ b/capture-sdk/sdk/src/main/java/net/gini/android/capture/analysis/AnalysisScreenPresenterExtension.kt @@ -173,7 +173,7 @@ internal class AnalysisScreenPresenterExtension( } } - private fun handleSaveInvoicesLocally( + fun handleSaveInvoicesLocally( mIsInvoiceSavingEnabled: Boolean, isSavingInvoicesInProgress: Boolean, resultHolder: ResultHolder, @@ -253,7 +253,7 @@ internal class AnalysisScreenPresenterExtension( if (educationMutex.isLocked) educationMutex.unlock() } - private fun doWhenEducationFinished(action: () -> Unit) { + fun doWhenEducationFinished(action: () -> Unit) { CoroutineScope(Dispatchers.IO).launch { educationMutex.withLock { withContext(Dispatchers.Main) { diff --git a/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisFragmentTest.kt b/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisFragmentTest.kt index 7e97ff98f..413f47d6c 100644 --- a/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisFragmentTest.kt +++ b/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisFragmentTest.kt @@ -8,12 +8,15 @@ import androidx.lifecycle.Lifecycle import androidx.navigation.Navigation import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.spy import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import net.gini.android.capture.Document import net.gini.android.capture.GiniCapture +import net.gini.android.capture.analysis.warning.WarningBottomSheet +import net.gini.android.capture.analysis.warning.WarningType import net.gini.android.capture.document.ImageDocument import net.gini.android.capture.tracking.AnalysisScreenEvent import net.gini.android.capture.tracking.Event @@ -75,4 +78,60 @@ class AnalysisFragmentTest { } } } + @Test + fun `showWarning displays bottom sheet with correct type and listener`() { + // Given + val eventTracker = spy() + GiniCapture.newInstance(InstrumentationRegistry.getInstrumentation().context) + .setEventTracker(eventTracker).build() + GiniCapture.getInstance().internal().imageMultiPageDocumentMemoryStore.setMultiPageDocument(mock()) + + val bundle = Bundle().apply { + putParcelable("GC_ARGS_DOCUMENT", mock().apply { + whenever(isReviewable).thenReturn(true) + whenever(type).thenReturn(Document.Type.IMAGE) + }) + putString("GC_ARGS_DOCUMENT_ANALYSIS_ERROR_MESSAGE", "") + } + + val mockOnProceed = mock() + + FragmentScenario.launchInContainer( + fragmentClass = AnalysisFragment::class.java, + fragmentArgs = bundle, + factory = object : FragmentFactory() { + override fun instantiate(classLoader: ClassLoader, className: String): Fragment { + return AnalysisFragment().apply { + setListener(mock()) + setBankSDKBridge(mock()) + setCancelListener(mock()) + }.also { fragment -> + fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner -> + if (viewLifecycleOwner != null) { + Navigation.setViewNavController(fragment.requireView(), mock()) + } + } + } + } + } + ).use { scenario -> + scenario.moveToState(Lifecycle.State.RESUMED) + + // When + scenario.onFragment { fragment -> + fragment.showWarning(WarningType.DOCUMENT_MARKED_AS_PAID, mockOnProceed) + + // Execute pending transactions to ensure the bottom sheet is added + val fragmentManager = fragment.requireActivity().supportFragmentManager + fragmentManager.executePendingTransactions() + + // Then + val warningSheet = fragmentManager.findFragmentByTag("WarningBottomSheet") + + assertThat(warningSheet).isNotNull() + assertThat(warningSheet).isInstanceOf(WarningBottomSheet::class.java) + } + } + } + } diff --git a/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisScreenPresenterTest.kt b/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisScreenPresenterTest.kt index f2968ff74..28f00cc24 100644 --- a/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisScreenPresenterTest.kt +++ b/capture-sdk/sdk/src/test/java/net/gini/android/capture/analysis/AnalysisScreenPresenterTest.kt @@ -12,6 +12,7 @@ import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.atLeast import com.nhaarman.mockitokotlin2.atLeastOnce +import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock @@ -23,6 +24,7 @@ import io.mockk.Runs import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.slot import io.mockk.spyk import io.mockk.verify import jersey.repackaged.jsr166e.CompletableFuture @@ -37,6 +39,7 @@ import net.gini.android.capture.BankSDKProperties import net.gini.android.capture.Document import net.gini.android.capture.GiniCapture import net.gini.android.capture.GiniCaptureHelper +import net.gini.android.capture.analysis.warning.WarningType import net.gini.android.capture.document.DocumentFactory import net.gini.android.capture.document.GiniCaptureDocument import net.gini.android.capture.document.ImageDocument @@ -62,6 +65,9 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations import java.util.Collections import java.util.concurrent.CancellationException +import kotlin.invoke +import kotlin.run +import kotlin.text.get /** * Created by Alpar Szotyori on 10.05.2019. @@ -924,6 +930,71 @@ class AnalysisScreenPresenterTest { verify { presenter.proceedWithExtractions(mockResultHolder) } } + @Test + fun `showAlreadyPaidHint calls handleSaveInvoicesLocally immediately when isSavingInvoicesInProgress is true`() { + val view = mockk(relaxed = true) + val presenter = spyk(AnalysisScreenPresenterExtension(view)) + val resultHolder = mockk(relaxed = true) + val activity = mockk(relaxed = true) + + every { presenter.handleSaveInvoicesLocally(any(), any(), any(), any()) } just Runs + + presenter.showAlreadyPaidHint( + mIsInvoiceSavingEnabled = true, + isSavingInvoicesInProgress = true, + resultHolder = resultHolder, + activity = activity + ) + + io.mockk.verify { + presenter.handleSaveInvoicesLocally( + mIsInvoiceSavingEnabled = true, + isSavingInvoicesInProgress = true, + resultHolder = resultHolder, + activity = activity + ) + } + } + + @Test + fun `showAlreadyPaidHint calls showAlreadyPaidWarning and handleSaveInvoicesLocally when isSavingInvoicesInProgress is false`() { + val view = mock() + val presenter = spy(AnalysisScreenPresenterExtension(view)) + val resultHolder = mock() + val activity = mock() + + // Use doAnswer for void methods on spies + doAnswer { invocation -> + val lambda = invocation.getArgument<() -> Unit>(0) + lambda.invoke() + null + }.whenever(presenter).doWhenEducationFinished(any()) + + // Capture the callback passed to showAlreadyPaidWarning + val callbackCaptor = argumentCaptor() + + presenter.showAlreadyPaidHint( + mIsInvoiceSavingEnabled = false, + isSavingInvoicesInProgress = false, + resultHolder = resultHolder, + activity = activity + ) + + // showAlreadyPaidWarning should be called + verify(view).showAlreadyPaidWarning(eq(WarningType.DOCUMENT_MARKED_AS_PAID), callbackCaptor.capture()) + + // Simulate user action on the warning + callbackCaptor.firstValue.run() + + // handleSaveInvoicesLocally should be called with isSavingInvoicesInProgress = false + verify(presenter).handleSaveInvoicesLocally( + mIsInvoiceSavingEnabled = false, + isSavingInvoicesInProgress = false, + resultHolder = resultHolder, + activity = activity + ) + } + @Test fun `proceedWithExtractions calls onExtractionsAvailable with correct arguments`() {