From 508bc0549cace6abc52c360554b8768f97eb0e2e Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Wed, 28 Feb 2024 11:53:11 +0200 Subject: [PATCH 1/4] Minor refactor in DocumentDetailsTransformer. --- .../transformer/DocumentDetailsTransformer.kt | 2 +- .../interactor/document/DocumentDetailsInteractor.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt index d8d41fca..715a40b0 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt @@ -35,7 +35,7 @@ import org.json.JSONObject object DocumentDetailsTransformer { - fun transformToUiItems( + fun transformToUiItem( document: Document, resourceProvider: ResourceProvider, docType: String, diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/interactor/document/DocumentDetailsInteractor.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/interactor/document/DocumentDetailsInteractor.kt index 867a1777..461fcc8c 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/interactor/document/DocumentDetailsInteractor.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/interactor/document/DocumentDetailsInteractor.kt @@ -68,12 +68,12 @@ class DocumentDetailsInteractorImpl( flow { val document = walletCoreDocumentsController.getDocumentById(id = documentId) document?.let { - val itemsUi = DocumentDetailsTransformer.transformToUiItems( + val itemUi = DocumentDetailsTransformer.transformToUiItem( document = it, resourceProvider = resourceProvider, docType = documentType ) - itemsUi?.let { documentUi -> + itemUi?.let { documentUi -> emit( DocumentDetailsInteractorPartialState.Success( documentUi = documentUi From e30c3c18362539ac28facb910127c2b68f50ce98 Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Wed, 28 Feb 2024 13:13:32 +0200 Subject: [PATCH 2/4] Replace usage of "`when`" with "whenever" in unit tests. --- .../controller/TestSecurityController.kt | 70 +++++++++---------- .../interactor/TestQuickPinInteractor.kt | 36 +++++----- .../interactor/TestDashboardInteractor.kt | 24 +++---- .../document/TestAddDocumentInteractor.kt | 20 +++--- .../java/eu/europa/ec/testfeature/Utils.kt | 10 +-- 5 files changed, 80 insertions(+), 80 deletions(-) diff --git a/business-logic/src/test/java/eu/europa/ec/businesslogic/controller/TestSecurityController.kt b/business-logic/src/test/java/eu/europa/ec/businesslogic/controller/TestSecurityController.kt index 269e7309..e38b13ae 100644 --- a/business-logic/src/test/java/eu/europa/ec/businesslogic/controller/TestSecurityController.kt +++ b/business-logic/src/test/java/eu/europa/ec/businesslogic/controller/TestSecurityController.kt @@ -30,9 +30,9 @@ import junit.framework.TestCase.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.Spy +import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @@ -72,131 +72,131 @@ class TestSecurityController { ) MockitoAnnotations.openMocks(this) - Mockito.`when`(configLogic.appBuildType).thenReturn(AppBuildType.RELEASE) - Mockito.`when`(androidPackageController.getSignatures()).thenReturn(listOf(signature)) + whenever(configLogic.appBuildType).thenReturn(AppBuildType.RELEASE) + whenever(androidPackageController.getSignatures()).thenReturn(listOf(signature)) } @Test fun testIsRunningOnEmulatorWithBlockEmulatorConfigTrue() { - Mockito.`when`(configSecurityLogic.blockEmulator).thenReturn(true) + whenever(configSecurityLogic.blockEmulator).thenReturn(true) assertTrue(securityController.isRunningOnEmulator()) } @Test fun testIsRunningOnEmulatorWithBlockEmulatorConfigFalse() { - Mockito.`when`(configSecurityLogic.blockEmulator).thenReturn(false) + whenever(configSecurityLogic.blockEmulator).thenReturn(false) assertFalse(securityController.isRunningOnEmulator()) } @Test fun testIsDeviceRootedWithBlockRootAccessConfigTrue() { - Mockito.`when`(configSecurityLogic.blockRootAccess).thenReturn(true) - Mockito.`when`(rootController.isRooted()).thenReturn(true) + whenever(configSecurityLogic.blockRootAccess).thenReturn(true) + whenever(rootController.isRooted()).thenReturn(true) assertTrue(securityController.isDeviceRooted()) } @Test fun testIsDeviceRootedWithBlockRootAccessConfigFalse() { - Mockito.`when`(configSecurityLogic.blockRootAccess).thenReturn(false) - Mockito.`when`(rootController.isRooted()).thenReturn(true) + whenever(configSecurityLogic.blockRootAccess).thenReturn(false) + whenever(rootController.isRooted()).thenReturn(true) assertFalse(securityController.isDeviceRooted()) } @Test fun testIsSignatureValidWithSignaturePackageNotNullAndValid() { - Mockito.`when`(configSecurityLogic.packageSignature).thenReturn(signature) + whenever(configSecurityLogic.packageSignature).thenReturn(signature) assertTrue(securityController.isSignatureValid()) } @Test fun testIsSignatureValidWithSignaturePackageNotNullAndNotValid() { - Mockito.`when`(configSecurityLogic.packageSignature).thenReturn("") + whenever(configSecurityLogic.packageSignature).thenReturn("") assertFalse(securityController.isSignatureValid()) } @Test fun testIsSignatureValidWithSignaturePackageConfigIsNull() { - Mockito.`when`(configSecurityLogic.packageSignature).thenReturn(null) + whenever(configSecurityLogic.packageSignature).thenReturn(null) assertTrue(securityController.isSignatureValid()) } @Test fun testIsPackageInstallerValidWithConfigPackageInstallersNotEmptyAndValidInstaller() { - Mockito.`when`(configSecurityLogic.packageInstallers).thenReturn(installers) - Mockito.`when`(androidPackageController.getInstaller(installers)) + whenever(configSecurityLogic.packageInstallers).thenReturn(installers) + whenever(androidPackageController.getInstaller(installers)) .thenReturn(AndroidInstaller.TRUSTED) assertTrue(securityController.isPackageInstallerValid()) } @Test fun testIsPackageInstallerValidWithConfigPackageInstallersNotEmptyAndInValidInstaller() { - Mockito.`when`(configSecurityLogic.packageInstallers).thenReturn(installers) - Mockito.`when`(androidPackageController.getInstaller(installers)) + whenever(configSecurityLogic.packageInstallers).thenReturn(installers) + whenever(androidPackageController.getInstaller(installers)) .thenReturn(AndroidInstaller.UNKNOWN) assertFalse(securityController.isPackageInstallerValid()) } @Test fun testIsPackageInstallerValidWithConfigPackageInstallersEmpty() { - Mockito.`when`(configSecurityLogic.packageInstallers).thenReturn(emptyList()) + whenever(configSecurityLogic.packageInstallers).thenReturn(emptyList()) assertTrue(securityController.isPackageInstallerValid()) } @Test fun testIsDebugModeEnabledWithConfigBlockDebugModeEnabledAndFlagDebuggable() { - Mockito.`when`(configSecurityLogic.blockDebugMode).thenReturn(true) - Mockito.`when`(androidPackageController.isDebugModeEnabled()).thenReturn(true) + whenever(configSecurityLogic.blockDebugMode).thenReturn(true) + whenever(androidPackageController.isDebugModeEnabled()).thenReturn(true) assertTrue(securityController.isDebugModeEnabled()) } @Test fun testIsDebugModeEnabledWithConfigBlockDebugModeEnabledAndFlagNotDebuggable() { - Mockito.`when`(configSecurityLogic.blockDebugMode).thenReturn(true) - Mockito.`when`(androidPackageController.isDebugModeEnabled()).thenReturn(false) + whenever(configSecurityLogic.blockDebugMode).thenReturn(true) + whenever(androidPackageController.isDebugModeEnabled()).thenReturn(false) assertFalse(securityController.isDebugModeEnabled()) } @Test fun testBlockScreenCaptureWithConfigBlockScreenCaptureEnabled() { - Mockito.`when`(configSecurityLogic.blockScreenCapture).thenReturn(true) + whenever(configSecurityLogic.blockScreenCapture).thenReturn(true) assertTrue(securityController.blockScreenCapture()) } @Test fun testBlockScreenCaptureWithConfigBlockScreenCaptureDisabled() { - Mockito.`when`(configSecurityLogic.blockScreenCapture).thenReturn(false) + whenever(configSecurityLogic.blockScreenCapture).thenReturn(false) assertFalse(securityController.blockScreenCapture()) } @Test fun testIsHookDetectedWithConfigBlockHooksEnabledAndReleaseBuildAndIsStackTracedHookDetectedAndIsMemoryHookedDetected() { - Mockito.`when`(configSecurityLogic.blockHooks).thenReturn(true) - Mockito.`when`(antiHookController.isMemoryHooked()).thenReturn(true) - Mockito.`when`(antiHookController.isStacktraceHooked()).thenReturn(true) + whenever(configSecurityLogic.blockHooks).thenReturn(true) + whenever(antiHookController.isMemoryHooked()).thenReturn(true) + whenever(antiHookController.isStacktraceHooked()).thenReturn(true) assertTrue(securityController.isHookDetected()) } @Test fun testIsHookDetectedWithConfigBlockHooksEnabledAndReleaseBuildAndIsStackTracedHookDetectedAndIsMemoryHookedNotDetected() { - Mockito.`when`(configSecurityLogic.blockHooks).thenReturn(true) - Mockito.`when`(antiHookController.isMemoryHooked()).thenReturn(false) - Mockito.`when`(antiHookController.isStacktraceHooked()).thenReturn(true) + whenever(configSecurityLogic.blockHooks).thenReturn(true) + whenever(antiHookController.isMemoryHooked()).thenReturn(false) + whenever(antiHookController.isStacktraceHooked()).thenReturn(true) assertTrue(securityController.isHookDetected()) } @Test fun testIsHookDetectedWithConfigBlockHooksEnabledAndReleaseBuildAndIsStackTracedHookNotDetectedAndIsMemoryHookedNotDetected() { - Mockito.`when`(configSecurityLogic.blockHooks).thenReturn(true) - Mockito.`when`(antiHookController.isMemoryHooked()).thenReturn(false) - Mockito.`when`(antiHookController.isStacktraceHooked()).thenReturn(false) + whenever(configSecurityLogic.blockHooks).thenReturn(true) + whenever(antiHookController.isMemoryHooked()).thenReturn(false) + whenever(antiHookController.isStacktraceHooked()).thenReturn(false) assertFalse(securityController.isHookDetected()) } @Test fun testIsHookDetectedWithConfigBlockHooksDisabledAndReleaseBuildAndIsStackTracedHookDetectedAndIsMemoryHookedDetected() { - Mockito.`when`(configSecurityLogic.blockHooks).thenReturn(false) - Mockito.`when`(antiHookController.isMemoryHooked()).thenReturn(true) - Mockito.`when`(antiHookController.isStacktraceHooked()).thenReturn(true) + whenever(configSecurityLogic.blockHooks).thenReturn(false) + whenever(antiHookController.isMemoryHooked()).thenReturn(true) + whenever(antiHookController.isStacktraceHooked()).thenReturn(true) assertFalse(securityController.isHookDetected()) } } \ No newline at end of file diff --git a/common-feature/src/test/java/eu/europa/ec/commonfeature/interactor/TestQuickPinInteractor.kt b/common-feature/src/test/java/eu/europa/ec/commonfeature/interactor/TestQuickPinInteractor.kt index b568fe43..3ba8aa0f 100644 --- a/common-feature/src/test/java/eu/europa/ec/commonfeature/interactor/TestQuickPinInteractor.kt +++ b/common-feature/src/test/java/eu/europa/ec/commonfeature/interactor/TestQuickPinInteractor.kt @@ -33,10 +33,10 @@ import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.anyString -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever class TestQuickPinInteractor { @@ -66,7 +66,7 @@ class TestQuickPinInteractor { resourceProvider = resourceProvider ) - `when`(resourceProvider.genericErrorMessage()) + whenever(resourceProvider.genericErrorMessage()) .thenReturn(mockedGenericErrorMessage) } @@ -82,7 +82,7 @@ class TestQuickPinInteractor { @Test fun `Given Case 1, When hasPin is called, Then it returns false`() { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenReturn(mockedEmptyPin) // When @@ -101,7 +101,7 @@ class TestQuickPinInteractor { @Test fun `Given Case 2, When hasPin is called, Then it returns false`() { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenReturn(mockedBlankPin) // When @@ -120,7 +120,7 @@ class TestQuickPinInteractor { @Test fun `Given Case 3, When hasPin is called, Then it returns true`() { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenReturn(mockedPin) // When @@ -170,7 +170,7 @@ class TestQuickPinInteractor { fun `Given Case 2, When setPin is called, Then it returns Failed with the appropriate error message`() { coroutineRule.runTest { // Given - `when`(resourceProvider.getString(R.string.quick_pin_non_match)) + whenever(resourceProvider.getString(R.string.quick_pin_non_match)) .thenReturn(mockedPinsDontMatchMessage) val mockedNewPin = mockedNewPin @@ -204,7 +204,7 @@ class TestQuickPinInteractor { // Given val mockedNewPin = mockedPin val mockedInitialPin = mockedPin - `when`(prefKeys.setDevicePin(anyString())) + whenever(prefKeys.setDevicePin(anyString())) .thenThrow(mockedExceptionWithMessage) // When @@ -232,7 +232,7 @@ class TestQuickPinInteractor { // Given val mockedNewPin = mockedPin val mockedInitialPin = mockedPin - `when`(prefKeys.setDevicePin(anyString())) + whenever(prefKeys.setDevicePin(anyString())) .thenThrow(mockedExceptionWithNoMessage) // When @@ -279,7 +279,7 @@ class TestQuickPinInteractor { fun `Given Case 2, When changePin is called, Then it returns Failed with exception's localized message`() { coroutineRule.runTest { // Given - `when`(prefKeys.setDevicePin(mockedNewPin)) + whenever(prefKeys.setDevicePin(mockedNewPin)) .thenThrow(mockedExceptionWithMessage) // When @@ -303,7 +303,7 @@ class TestQuickPinInteractor { fun `Given Case 3, When changePin is called, Then it returns Failed with the generic error message`() { coroutineRule.runTest { // Given - `when`(prefKeys.setDevicePin(mockedNewPin)) + whenever(prefKeys.setDevicePin(mockedNewPin)) .thenThrow(mockedExceptionWithNoMessage) // When @@ -331,7 +331,7 @@ class TestQuickPinInteractor { fun `Given Case 1, When isCurrentPinValid is called, Then it returns Success`() { coroutineRule.runTest { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenReturn(mockedPin) // When @@ -354,9 +354,9 @@ class TestQuickPinInteractor { fun `Given Case 2, When isCurrentPinValid is called, Then it returns Failed with the appropriate error message`() { coroutineRule.runTest { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenReturn(mockedPin) - `when`(resourceProvider.getString(R.string.quick_pin_invalid_error)) + whenever(resourceProvider.getString(R.string.quick_pin_invalid_error)) .thenReturn(mockedInvalidPinMessage) // When @@ -383,7 +383,7 @@ class TestQuickPinInteractor { fun `Given Case 3, When isCurrentPinValid is called, Then it returns Failed with exception's localized message`() { coroutineRule.runTest { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenThrow(mockedExceptionWithMessage) // When @@ -407,7 +407,7 @@ class TestQuickPinInteractor { fun `Given Case 4, When isCurrentPinValid is called, Then it returns Failed with the generic error message`() { coroutineRule.runTest { // Given - `when`(prefKeys.getDevicePin()) + whenever(prefKeys.getDevicePin()) .thenThrow(mockedExceptionWithNoMessage) // When @@ -457,7 +457,7 @@ class TestQuickPinInteractor { fun `Given Case 2, When isPinMatched is called, Then it returns Failed with the appropriate error message`() { coroutineRule.runTest { // Given - `when`(resourceProvider.getString(R.string.quick_pin_invalid_error)) + whenever(resourceProvider.getString(R.string.quick_pin_invalid_error)) .thenReturn(mockedInvalidPinMessage) // When @@ -485,7 +485,7 @@ class TestQuickPinInteractor { fun `Given Case 3, When isPinMatched is called, Then it returns Failed with exception's localized message`() { coroutineRule.runTest { // Given - `when`(resourceProvider.getString(R.string.quick_pin_invalid_error)) + whenever(resourceProvider.getString(R.string.quick_pin_invalid_error)) .thenThrow(mockedExceptionWithMessage) // When @@ -510,7 +510,7 @@ class TestQuickPinInteractor { fun `Given Case 4, When isPinMatched is called, Then it returns Failed with the generic error message`() { coroutineRule.runTest { // Given - `when`(resourceProvider.getString(R.string.quick_pin_invalid_error)) + whenever(resourceProvider.getString(R.string.quick_pin_invalid_error)) .thenThrow(mockedExceptionWithNoMessage) // When diff --git a/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt b/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt index d22290ed..e1c84b5f 100644 --- a/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt +++ b/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt @@ -52,10 +52,10 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.Shadows import org.robolectric.annotation.Config @@ -99,8 +99,8 @@ class TestDashboardInteractor { configLogic = configLogic ) - `when`(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) - `when`(resourceProvider.provideContext()).thenReturn(getMockedContext()) + whenever(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) + whenever(resourceProvider.provideContext()).thenReturn(getMockedContext()) bluetoothManager = getMockedContext().getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager @@ -158,7 +158,7 @@ class TestDashboardInteractor { bleTransferMode(EudiWalletConfig.BLE_CLIENT_CENTRAL_MODE) } - `when`(walletCoreConfig.config).thenReturn(mockedConfig) + whenever(walletCoreConfig.config).thenReturn(mockedConfig) // When val actual = interactor.isBleCentralClientModeEnabled() @@ -178,7 +178,7 @@ class TestDashboardInteractor { bleTransferMode(EudiWalletConfig.BLE_SERVER_PERIPHERAL_MODE) } - `when`(walletCoreConfig.config).thenReturn(mockedConfig) + whenever(walletCoreConfig.config).thenReturn(mockedConfig) // When val actual = interactor.isBleCentralClientModeEnabled() @@ -205,7 +205,7 @@ class TestDashboardInteractor { // Given mockGetStringForDocumentsCall(resourceProvider) - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(mockedFullDocuments) // When @@ -239,7 +239,7 @@ class TestDashboardInteractor { // Given mockGetStringForDocumentsCall(resourceProvider) - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(listOf(mockedMdlWithNoUserNameAndNoUserImage)) // When @@ -272,7 +272,7 @@ class TestDashboardInteractor { // Given mockGetStringForDocumentsCall(resourceProvider) - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(listOf(mockedMdlWithNoExpirationDate)) // When @@ -296,7 +296,7 @@ class TestDashboardInteractor { fun `Given Case 4, When getDocuments is called, Then it returns Failed with exception's localized message`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenThrow(mockedExceptionWithMessage) // When @@ -318,7 +318,7 @@ class TestDashboardInteractor { fun `Given Case 5, When getDocuments is called, Then it returns Failed with the generic error message`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenThrow(mockedExceptionWithNoMessage) // When @@ -340,7 +340,7 @@ class TestDashboardInteractor { fun `Given an App Version, When getAppVersion is called, Then it returns the Apps Version`() { // Given val expectedAppVersion = "2024.01.1" - `when`(configLogic.appVersion) + whenever(configLogic.appVersion) .thenReturn(expectedAppVersion) // When @@ -366,7 +366,7 @@ class TestDashboardInteractor { private fun mockGetStringForDocumentsCall(resourceProvider: ResourceProvider) { mockDocumentTypeUiToUiNameCall(resourceProvider) - `when`(resourceProvider.getString(R.string.dashboard_document_no_expiration_found)) + whenever(resourceProvider.getString(R.string.dashboard_document_no_expiration_found)) .thenReturn(mockedNoExpirationDateFound) } //endregion diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index 3e8816d9..52e3e958 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -43,10 +43,10 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.kotlin.times import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever import kotlin.test.assertEquals class TestAddDocumentInteractor { @@ -77,7 +77,7 @@ class TestAddDocumentInteractor { resourceProvider = resourceProvider, ) - `when`(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) + whenever(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) } @After @@ -100,7 +100,7 @@ class TestAddDocumentInteractor { fun `Given Case 1, When getAddDocumentOption is called, Then Case 1 Expected Result is returned`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(emptyList()) mockDocumentTypeUiToUiNameCall(resourceProvider) @@ -137,7 +137,7 @@ class TestAddDocumentInteractor { fun `Given Case 2, When getAddDocumentOption is called, Then Case 2 Expected Result is returned`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(listOf(mockedFullMdl)) mockDocumentTypeUiToUiNameCall(resourceProvider) @@ -173,7 +173,7 @@ class TestAddDocumentInteractor { fun `Given Case 3, When getAddDocumentOption is called, Then Case 3 Expected Result is returned`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(emptyList()) mockDocumentTypeUiToUiNameCall(resourceProvider) @@ -207,7 +207,7 @@ class TestAddDocumentInteractor { fun `Given Case 4, When getAddDocumentOption is called, Then Case 4 Expected Result is returned`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenReturn(listOf(mockedFullPid)) mockDocumentTypeUiToUiNameCall(resourceProvider) @@ -237,7 +237,7 @@ class TestAddDocumentInteractor { fun `Given Case 5, When getAddDocumentOption is called, Then it returns Failure with exception's localized message`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenThrow(mockedExceptionWithMessage) // When @@ -261,7 +261,7 @@ class TestAddDocumentInteractor { fun `Given Case 6, When getAddDocumentOption is called, Then it returns Failure with the generic error message`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.getAllDocuments()) + whenever(walletCoreDocumentsController.getAllDocuments()) .thenThrow(mockedExceptionWithNoMessage) // When @@ -288,7 +288,7 @@ class TestAddDocumentInteractor { val mockedIssuanceMethod = IssuanceMethod.OPENID4VCI val mockedDocumentType = DocumentTypeUi.PID.docType - `when`( + whenever( walletCoreDocumentsController.issueDocument( issuanceMethod = mockedIssuanceMethod, documentType = mockedDocumentType @@ -318,7 +318,7 @@ class TestAddDocumentInteractor { fun `When addSampleData is called, Then it calls walletCoreDocumentsController#addSampleData`() { coroutineRule.runTest { // Given - `when`(walletCoreDocumentsController.addSampleData()) + whenever(walletCoreDocumentsController.addSampleData()) .thenReturn(AddSampleDataPartialState.Success.toFlow()) // When diff --git a/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt b/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt index 0df368e1..0268bff8 100644 --- a/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt +++ b/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt @@ -19,7 +19,7 @@ package eu.europa.ec.testfeature import androidx.annotation.VisibleForTesting import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider -import org.mockito.Mockito.`when` +import org.mockito.kotlin.whenever private const val mockedDocUiNamePid = "National ID" private const val mockedDocUiNameMdl = "Driving License" @@ -31,15 +31,15 @@ private const val mockedDocUiNameSampleData = "Load Sample Documents" */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun mockDocumentTypeUiToUiNameCall(resourceProvider: ResourceProvider) { - `when`(resourceProvider.getString(R.string.pid)) + whenever(resourceProvider.getString(R.string.pid)) .thenReturn(mockedDocUiNamePid) - `when`(resourceProvider.getString(R.string.mdl)) + whenever(resourceProvider.getString(R.string.mdl)) .thenReturn(mockedDocUiNameMdl) - `when`(resourceProvider.getString(R.string.conference_badge)) + whenever(resourceProvider.getString(R.string.conference_badge)) .thenReturn(mockedDocUiNameConferenceBadge) - `when`(resourceProvider.getString(R.string.load_sample_data)) + whenever(resourceProvider.getString(R.string.load_sample_data)) .thenReturn(mockedDocUiNameSampleData) } \ No newline at end of file From 32bcea9b933dff15c1c5b862805ea5c92d2bd2a9 Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Wed, 28 Feb 2024 18:41:51 +0200 Subject: [PATCH 3/4] Adds unit tests for DocumentDetails Interactor. --- .../transformer/DocumentDetailsTransformer.kt | 9 +- .../ec/commonfeature/util/DocumentHelper.kt | 10 +- .../ec/commonfeature/util/TestsConstants.kt | 133 +++++ .../interactor/TestDashboardInteractor.kt | 2 +- .../document/details/DocumentDetailsScreen.kt | 34 +- .../details/DocumentDetailsViewModel.kt | 8 +- .../document/TestAddDocumentInteractor.kt | 2 +- .../document/TestDocumentDetailsInteractor.kt | 507 ++++++++++++++++++ .../eu/europa/ec/testfeature/Constants.kt | 243 ++++++++- .../java/eu/europa/ec/testfeature/Utils.kt | 72 ++- .../component/InfoTextWithNameAndValue.kt | 22 + 11 files changed, 1000 insertions(+), 42 deletions(-) create mode 100644 issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentDetailsInteractor.kt diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt index 715a40b0..410bad1d 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/ui/document_details/transformer/DocumentDetailsTransformer.kt @@ -21,6 +21,7 @@ import eu.europa.ec.businesslogic.util.toDateFormatted import eu.europa.ec.businesslogic.util.toList import eu.europa.ec.commonfeature.model.DocumentUi import eu.europa.ec.commonfeature.model.toDocumentTypeUi +import eu.europa.ec.commonfeature.model.toUiName import eu.europa.ec.commonfeature.ui.document_details.model.DocumentDetailsUi import eu.europa.ec.commonfeature.ui.document_details.model.DocumentJsonKeys import eu.europa.ec.commonfeature.util.extractFullNameFromDocumentOrEmpty @@ -68,13 +69,15 @@ object DocumentDetailsTransformer { ) } + val documentTypeUi = document.docType.toDocumentTypeUi() + return DocumentUi( documentId = document.id, - documentName = document.name, - documentType = document.docType.toDocumentTypeUi(), + documentName = documentTypeUi.toUiName(resourceProvider), + documentType = documentTypeUi, documentExpirationDateFormatted = documentJson.getStringFromJsonOrEmpty( key = DocumentJsonKeys.EXPIRY_DATE - ).toDateFormatted().toString(), + ).toDateFormatted() ?: "", documentImage = documentJson.getStringFromJsonOrEmpty( key = DocumentJsonKeys.PORTRAIT ), diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/DocumentHelper.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/DocumentHelper.kt index 77798295..27709ad8 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/DocumentHelper.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/DocumentHelper.kt @@ -51,7 +51,15 @@ fun extractFullNameFromDocumentOrEmpty(document: Document): String { document = document, key = DocumentJsonKeys.LAST_NAME ) - return "$firstName $lastName" + return if (firstName.isNotBlank() && lastName.isNotBlank()) { + "$firstName $lastName" + } else if (firstName.isNotBlank()) { + firstName + } else if (lastName.isNotBlank()) { + lastName + } else { + "" + } } fun keyIsBase64(key: String): Boolean { diff --git a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsConstants.kt b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsConstants.kt index f20dbc85..3d670b5e 100644 --- a/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsConstants.kt +++ b/common-feature/src/main/java/eu/europa/ec/commonfeature/util/TestsConstants.kt @@ -20,7 +20,10 @@ import androidx.annotation.VisibleForTesting import eu.europa.ec.commonfeature.model.DocumentOptionItemUi import eu.europa.ec.commonfeature.model.DocumentTypeUi import eu.europa.ec.commonfeature.model.DocumentUi +import eu.europa.ec.commonfeature.ui.document_details.model.DocumentDetailsUi import eu.europa.ec.uilogic.component.AppIcons +import eu.europa.ec.uilogic.component.InfoTextWithNameAndImageData +import eu.europa.ec.uilogic.component.InfoTextWithNameAndValueData @VisibleForTesting(otherwise = VisibleForTesting.NONE) object TestsConstants { @@ -45,6 +48,68 @@ object TestsConstants { documentDetails = emptyList(), ) + val mockedBasicPidUi = mockedFullPidUi.copy( + documentDetails = listOf( + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "birth_city", + infoValues = arrayOf("KATRINEHOLM") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "gender", + infoValues = arrayOf("male") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "age_over_18", + infoValues = arrayOf("yes") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "age_birth_year", + infoValues = arrayOf("1985") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "expiry_date", + infoValues = arrayOf("30 Mar 2050") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "given_name", + infoValues = arrayOf("JAN") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "family_name", + infoValues = arrayOf("ANDERSSON") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "age_over_65", + infoValues = arrayOf("no") + ) + ), + ), + userFullName = "JAN ANDERSSON" + ) + val mockedFullMdlUi = DocumentUi( documentId = mockedId2, documentName = mockedDocUiNameMdl, @@ -54,6 +119,74 @@ object TestsConstants { documentDetails = emptyList(), ) + val mockedBasicMdlUi = mockedFullMdlUi.copy( + documentDetails = listOf( + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "driving_privileges", + infoValues = arrayOf( + "issue_date: 1 Jul 2010\n" + + "expiry_date: 30 Mar 2050\n" + + "vehicle_category_code: A\n" + + "issue_date: 19 May 2008\n" + + "expiry_date: 30 Mar 2050\n" + + "vehicle_category_code: B" + ) + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "expiry_date", + infoValues = arrayOf("30 Mar 2050") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "sex", + infoValues = arrayOf("male") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "birth_place", + infoValues = arrayOf("SWEDEN") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "given_name", + infoValues = arrayOf("JAN") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "portrait", + infoValues = arrayOf("Shown above") + ) + ), + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData + .create( + title = "family_name", + infoValues = arrayOf("ANDERSSON") + ) + ), + DocumentDetailsUi.SignatureItem( + itemData = InfoTextWithNameAndImageData( + title = "signature_usual_mark", + base64Image = "SE" + ) + ), + ), + userFullName = "JAN ANDERSSON" + ) + val mockedMdlUiWithNoUserNameAndNoUserImage: DocumentUi = mockedFullMdlUi val mockedMdlUiWithNoExpirationDate: DocumentUi = mockedFullMdlUi.copy( diff --git a/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt b/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt index e1c84b5f..0ed08d85 100644 --- a/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt +++ b/dashboard-feature/src/test/java/eu/europa/ec/dashboardfeature/interactor/TestDashboardInteractor.kt @@ -33,7 +33,7 @@ import eu.europa.ec.commonfeature.util.TestsConstants.mockedUserFirstName import eu.europa.ec.eudi.wallet.EudiWalletConfig import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider -import eu.europa.ec.testfeature.mockDocumentTypeUiToUiNameCall +import eu.europa.ec.testfeature.MockResourceProviderForStringCalls.mockDocumentTypeUiToUiNameCall import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedFullDocuments diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt index 8e3a1dd5..20e0565d 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt @@ -138,7 +138,7 @@ fun DocumentDetailsScreen( sheetState = bottomSheetState ) { SheetContent( - documentTypeUiName = state.documentTypeUiName, + documentTypeUiName = state.document?.documentName, onEventSent = { viewModel.setEvent(it) } @@ -274,23 +274,25 @@ private fun Content( @Composable private fun SheetContent( - documentTypeUiName: String, + documentTypeUiName: String?, onEventSent: (event: Event) -> Unit ) { - DialogBottomSheet( - title = stringResource( - id = R.string.document_details_bottom_sheet_delete_title, - documentTypeUiName - ), - message = stringResource( - id = R.string.document_details_bottom_sheet_delete_subtitle, - documentTypeUiName - ), - positiveButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_primary_button_text), - negativeButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_secondary_button_text), - onPositiveClick = { onEventSent(Event.BottomSheet.Delete.PrimaryButtonPressed) }, - onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) } - ) + documentTypeUiName?.let { + DialogBottomSheet( + title = stringResource( + id = R.string.document_details_bottom_sheet_delete_title, + it + ), + message = stringResource( + id = R.string.document_details_bottom_sheet_delete_subtitle, + it + ), + positiveButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_primary_button_text), + negativeButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_secondary_button_text), + onPositiveClick = { onEventSent(Event.BottomSheet.Delete.PrimaryButtonPressed) }, + onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) } + ) + } } @Composable diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsViewModel.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsViewModel.kt index 3291a9fa..452f9f03 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsViewModel.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsViewModel.kt @@ -19,11 +19,9 @@ package eu.europa.ec.issuancefeature.ui.document.details import androidx.lifecycle.viewModelScope import eu.europa.ec.commonfeature.config.IssuanceFlowUiConfig import eu.europa.ec.commonfeature.model.DocumentUi -import eu.europa.ec.commonfeature.model.toUiName import eu.europa.ec.issuancefeature.interactor.document.DocumentDetailsInteractor import eu.europa.ec.issuancefeature.interactor.document.DocumentDetailsInteractorDeleteDocumentPartialState import eu.europa.ec.issuancefeature.interactor.document.DocumentDetailsInteractorPartialState -import eu.europa.ec.resourceslogic.provider.ResourceProvider import eu.europa.ec.uilogic.component.AppIcons import eu.europa.ec.uilogic.component.HeaderData import eu.europa.ec.uilogic.component.content.ContentErrorConfig @@ -53,7 +51,6 @@ data class State( val isBottomSheetOpen: Boolean = false, val document: DocumentUi? = null, - val documentTypeUiName: String = "", val headerData: HeaderData? = null ) : ViewState @@ -93,7 +90,6 @@ sealed class Effect : ViewSideEffect { @KoinViewModel class DocumentDetailsViewModel( private val documentDetailsInteractor: DocumentDetailsInteractor, - private val resourceProvider: ResourceProvider, @InjectedParam private val detailsType: IssuanceFlowUiConfig, @InjectedParam private val documentId: String, @InjectedParam private val documentType: String, @@ -166,15 +162,13 @@ class DocumentDetailsViewModel( when (response) { is DocumentDetailsInteractorPartialState.Success -> { val documentUi = response.documentUi - val documentTypeUiName = documentUi.documentType.toUiName(resourceProvider) setState { copy( isLoading = false, error = null, document = documentUi, - documentTypeUiName = documentTypeUiName, headerData = HeaderData( - title = documentTypeUiName, + title = documentUi.documentName, subtitle = documentUi.userFullName.orEmpty(), base64Image = documentUi.documentImage, icon = AppIcons.IdStroke diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt index 52e3e958..1d2db689 100644 --- a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestAddDocumentInteractor.kt @@ -28,7 +28,7 @@ import eu.europa.ec.commonfeature.util.TestsConstants.mockedMdlOptionItemUi import eu.europa.ec.commonfeature.util.TestsConstants.mockedPidOptionItemUi import eu.europa.ec.commonfeature.util.TestsConstants.mockedSampleDataOptionItemUi import eu.europa.ec.resourceslogic.provider.ResourceProvider -import eu.europa.ec.testfeature.mockDocumentTypeUiToUiNameCall +import eu.europa.ec.testfeature.MockResourceProviderForStringCalls.mockDocumentTypeUiToUiNameCall import eu.europa.ec.testfeature.mockedExceptionWithMessage import eu.europa.ec.testfeature.mockedExceptionWithNoMessage import eu.europa.ec.testfeature.mockedFullMdl diff --git a/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentDetailsInteractor.kt b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentDetailsInteractor.kt new file mode 100644 index 00000000..1c5b2d02 --- /dev/null +++ b/issuance-feature/src/test/java/eu/europa/ec/issuancefeature/interactor/document/TestDocumentDetailsInteractor.kt @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2023 European Commission + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European + * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work + * except in compliance with the Licence. + * + * You may obtain a copy of the Licence at: + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the Licence for the specific language + * governing permissions and limitations under the Licence. + */ + +package eu.europa.ec.issuancefeature.interactor.document + +import eu.europa.ec.businesslogic.controller.walletcore.DeleteAllDocumentsPartialState +import eu.europa.ec.businesslogic.controller.walletcore.DeleteDocumentPartialState +import eu.europa.ec.businesslogic.controller.walletcore.WalletCoreDocumentsController +import eu.europa.ec.commonfeature.model.DocumentTypeUi +import eu.europa.ec.commonfeature.model.DocumentUi +import eu.europa.ec.commonfeature.ui.document_details.model.DocumentDetailsUi +import eu.europa.ec.commonfeature.util.TestsConstants +import eu.europa.ec.commonfeature.util.TestsConstants.mockedBasicMdlUi +import eu.europa.ec.commonfeature.util.TestsConstants.mockedBasicPidUi +import eu.europa.ec.eudi.wallet.document.Document +import eu.europa.ec.resourceslogic.provider.ResourceProvider +import eu.europa.ec.testfeature.MockResourceProviderForStringCalls.mockDocumentTypeUiToUiNameCall +import eu.europa.ec.testfeature.MockResourceProviderForStringCalls.mockTransformToUiItemCall +import eu.europa.ec.testfeature.mockedEmptyPid +import eu.europa.ec.testfeature.mockedExceptionWithMessage +import eu.europa.ec.testfeature.mockedExceptionWithNoMessage +import eu.europa.ec.testfeature.mockedGenericErrorMessage +import eu.europa.ec.testfeature.mockedId1 +import eu.europa.ec.testfeature.mockedId2 +import eu.europa.ec.testfeature.mockedMdlCodeName +import eu.europa.ec.testfeature.mockedMdlWithBasicFields +import eu.europa.ec.testfeature.mockedPidCodeName +import eu.europa.ec.testfeature.mockedPidWithBasicFields +import eu.europa.ec.testfeature.mockedPlainFailureMessage +import eu.europa.ec.testlogic.extension.runFlowTest +import eu.europa.ec.testlogic.extension.runTest +import eu.europa.ec.testlogic.extension.toFlow +import eu.europa.ec.testlogic.rule.CoroutineTestRule +import eu.europa.ec.uilogic.component.InfoTextWithNameAndValueData +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.whenever +import kotlin.test.assertEquals + +class TestDocumentDetailsInteractor { + + @get:Rule + val coroutineRule = CoroutineTestRule() + + @Mock + private lateinit var walletCoreDocumentsController: WalletCoreDocumentsController + + @Mock + private lateinit var resourceProvider: ResourceProvider + + private lateinit var interactor: DocumentDetailsInteractor + + private lateinit var closeable: AutoCloseable + + @Before + fun before() { + closeable = MockitoAnnotations.openMocks(this) + + interactor = DocumentDetailsInteractorImpl( + walletCoreDocumentsController = walletCoreDocumentsController, + resourceProvider = resourceProvider, + ) + + whenever(resourceProvider.genericErrorMessage()).thenReturn(mockedGenericErrorMessage) + } + + @After + fun after() { + closeable.close() + } + + //region getDocumentDetails + + // Case 1: + // 1. walletCoreDocumentsController.getDocumentById() returns a PID document. + + // Case 1 Expected Result: + // DocumentDetailsInteractorPartialState.Success state, with a PID document UI item. + @Test + fun `Given Case 1, When getDocumentDetails is called, Then Case 1 Expected Result is returned`() { + coroutineRule.runTest { + // Given + mockTransformToUiItemCall(resourceProvider) + mockDocumentTypeUiToUiNameCall(resourceProvider) + + mockGetDocumentByIdCall(response = mockedPidWithBasicFields) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Success( + documentUi = mockedBasicPidUi + ), + awaitItem() + ) + } + } + } + + // Case 2: + // 1. walletCoreDocumentsController.getDocumentById() returns an mDL document. + + // Case 2 Expected Result: + // DocumentDetailsInteractorPartialState.Success state, with an mDL document UI item. + @Test + fun `Given Case 2, When getDocumentDetails is called, Then Case 2 Expected Result is returned`() { + coroutineRule.runTest { + // Given + mockTransformToUiItemCall(resourceProvider) + mockDocumentTypeUiToUiNameCall(resourceProvider) + + mockGetDocumentByIdCall(response = mockedMdlWithBasicFields) + + // When + interactor.getDocumentDetails( + documentId = mockedId2, + documentType = mockedMdlCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Success( + documentUi = mockedBasicMdlUi.copy( + documentImage = "SE" + ) + ), + awaitItem() + ) + } + } + } + + // Case 3: + // 1. walletCoreDocumentsController.getDocumentById() returns an empty PID document. + + // Case 3 Expected Result: + // DocumentDetailsInteractorPartialState.Failure state, + // with the generic error message. + @Test + fun `Given Case 3, When getDocumentDetails is called, Then Case 3 Expected Result is returned`() { + coroutineRule.runTest { + // Given + mockGetDocumentByIdCall(response = mockedEmptyPid) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Failure( + error = mockedGenericErrorMessage + ), + awaitItem() + ) + } + } + } + + // Case 4: + // 1. walletCoreDocumentsController.getDocumentById() returns null. + + // Case 4 Expected Result: + // DocumentDetailsInteractorPartialState.Failure state, + // with the generic error message. + @Test + fun `Given Case 4, When getDocumentDetails is called, Then Case 4 Expected Result is returned`() { + coroutineRule.runTest { + // Given + mockGetDocumentByIdCall(response = null) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Failure( + error = mockedGenericErrorMessage + ), + awaitItem() + ) + } + } + } + + // Case 5: + // 1. walletCoreDocumentsController.getDocumentById() returns a PID document, with: + // no expiration date, + // no image, and + // no user name. + + // Case 5 Expected Result: + // DocumentDetailsInteractorPartialState.Success state, with a PID document UI item, with: + // an empty string for documentExpirationDateFormatted, + // an empty string for documentImage, and + // an empty string for userFullName, + @Test + fun `Given Case 5, When getDocumentDetails is called, Then Case 5 Expected Result is returned`() { + coroutineRule.runTest { + // Given + mockTransformToUiItemCall(resourceProvider) + mockDocumentTypeUiToUiNameCall(resourceProvider) + + mockGetDocumentByIdCall( + response = mockedPidWithBasicFields.copy( + nameSpacedData = mapOf( + mockedPidCodeName to mapOf( + "no_data_item" to byteArrayOf(0) + ) + ) + ) + ) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Success( + documentUi = DocumentUi( + documentId = TestsConstants.mockedId1, + documentName = TestsConstants.mockedDocUiNamePid, + documentType = DocumentTypeUi.PID, + documentExpirationDateFormatted = "", + documentImage = "", + documentDetails = listOf( + DocumentDetailsUi.DefaultItem( + itemData = InfoTextWithNameAndValueData.create( + title = "no_data_item", + infoValues = arrayOf("0") + ) + ) + ), + userFullName = "" + ) + ), + awaitItem() + ) + } + } + } + + // Case 6: + // 1. walletCoreDocumentsController.getDocumentById() throws an exception with a message. + + // Case 6 Expected Result: + // DocumentDetailsInteractorPartialState.Failure state, + // with the exception's localized message. + @Test + fun `Given Case 6, When getDocumentDetails is called, Then Case 6 Expected Result is returned`() { + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.getDocumentById(mockedId1)) + .thenThrow(mockedExceptionWithMessage) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Failure( + error = mockedExceptionWithMessage.localizedMessage!! + ), + awaitItem() + ) + } + } + } + + // Case 7: + // 1. walletCoreDocumentsController.getDocumentById() throws an exception with no message. + + // Case 7 Expected Result: + // DocumentDetailsInteractorPartialState.Failure state, + // with the generic error message. + @Test + fun `Given Case 7, When getDocumentDetails is called, Then Case 7 Expected Result is returned`() { + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.getDocumentById(mockedId1)) + .thenThrow(mockedExceptionWithNoMessage) + + // When + interactor.getDocumentDetails( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorPartialState.Failure( + error = mockedGenericErrorMessage + ), + awaitItem() + ) + } + } + } + //endregion + + //region deleteDocument + + // Case 1: + + // 1. A documentId and document is PID. + // 2. walletCoreDocumentsController.deleteAllDocuments() returns Failure. + @Test + fun `Given Case 1, When deleteDocument is called, Then it returns Failure with failure's error message`() { + coroutineRule.runTest { + // Given + mockDeleteAllDocumentsCall( + response = DeleteAllDocumentsPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) + ) + + // When + interactor.deleteDocument( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ), + awaitItem() + ) + } + } + } + + // Case 2: + + // 1. A documentId and document is PID. + // 2. walletCoreDocumentsController.deleteAllDocuments() returns Success. + @Test + fun `Given Case 2, When deleteDocument is called, Then it returns AllDocumentsDeleted`() { + coroutineRule.runTest { + // Given + mockDeleteAllDocumentsCall(response = DeleteAllDocumentsPartialState.Success) + + // When + interactor.deleteDocument( + documentId = mockedId1, + documentType = mockedPidCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.AllDocumentsDeleted, + awaitItem() + ) + } + } + } + + // Case 3: + + // 1. A documentId and document is mDL. + // 2. walletCoreDocumentsController.deleteDocument() returns Failure. + @Test + fun `Given Case 3, When deleteDocument is called, Then it returns Failure with failure's error message`() { + coroutineRule.runTest { + // Given + mockDeleteDocumentCall( + response = DeleteDocumentPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ) + ) + + // When + interactor.deleteDocument( + documentId = mockedId2, + documentType = mockedMdlCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.Failure( + errorMessage = mockedPlainFailureMessage + ), + awaitItem() + ) + } + } + } + + // Case 4: + + // 1. A documentId and document is mDL. + // 2. walletCoreDocumentsController.deleteDocument() returns Success. + @Test + fun `Given Case 4, When deleteDocument is called, Then it returns SingleDocumentDeleted`() { + coroutineRule.runTest { + // Given + mockDeleteDocumentCall(response = DeleteDocumentPartialState.Success) + + // When + interactor.deleteDocument( + documentId = mockedId2, + documentType = mockedMdlCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.SingleDocumentDeleted, + awaitItem() + ) + } + } + } + + // Case 5: + + // 1. A documentId and document is mDL. + // 2. walletCoreDocumentsController.deleteDocument() throws an exception with a message. + @Test + fun `Given Case 5, When deleteDocument is called, Then it returns Failure with the exception's localized message`() { + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.deleteDocument(mockedId2)) + .thenThrow(mockedExceptionWithMessage) + + // When + interactor.deleteDocument( + documentId = mockedId2, + documentType = mockedMdlCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.Failure( + errorMessage = mockedExceptionWithMessage.localizedMessage!! + ), + awaitItem() + ) + } + } + } + + // Case 6: + + // 1. A documentId and document is mDL. + // 2. walletCoreDocumentsController.deleteDocument() throws an exception with no message. + @Test + fun `Given Case 6, When deleteDocument is called, Then it returns Failure with the generic error message`() { + coroutineRule.runTest { + // Given + whenever(walletCoreDocumentsController.deleteDocument(mockedId2)) + .thenThrow(mockedExceptionWithNoMessage) + + // When + interactor.deleteDocument( + documentId = mockedId2, + documentType = mockedMdlCodeName + ).runFlowTest { + // Then + assertEquals( + DocumentDetailsInteractorDeleteDocumentPartialState.Failure( + errorMessage = mockedGenericErrorMessage + ), + awaitItem() + ) + } + } + } + //endregion + + private fun mockGetDocumentByIdCall(response: Document?) { + whenever(walletCoreDocumentsController.getDocumentById(anyString())) + .thenReturn(response) + } + + private fun mockDeleteAllDocumentsCall(response: DeleteAllDocumentsPartialState) { + whenever(walletCoreDocumentsController.deleteAllDocuments(anyString())) + .thenReturn(response.toFlow()) + } + + private fun mockDeleteDocumentCall(response: DeleteDocumentPartialState) { + whenever(walletCoreDocumentsController.deleteDocument(anyString())) + .thenReturn(response.toFlow()) + } +} \ No newline at end of file diff --git a/test-feature/src/main/java/eu/europa/ec/testfeature/Constants.kt b/test-feature/src/main/java/eu/europa/ec/testfeature/Constants.kt index c426da12..68e139ca 100644 --- a/test-feature/src/main/java/eu/europa/ec/testfeature/Constants.kt +++ b/test-feature/src/main/java/eu/europa/ec/testfeature/Constants.kt @@ -20,7 +20,7 @@ import eu.europa.ec.eudi.wallet.document.Document import java.time.Instant const val mockedGenericErrorMessage = "resourceProvider's genericErrorMessage" -const val plainFailureMessage = "failure message" +const val mockedPlainFailureMessage = "failure message" val mockedExceptionWithMessage = RuntimeException("Exception to test interactor.") val mockedExceptionWithNoMessage = RuntimeException() @@ -417,6 +417,229 @@ val mockedMdlFields: Map = mapOf( "age_over_68" to byteArrayOf(-12), ) +val mockedPidBasicFields: Map = mapOf( + "family_name" to byteArrayOf(105, 65, 78, 68, 69, 82, 83, 83, 79, 78), + "given_name" to byteArrayOf(99, 74, 65, 78), + "age_over_18" to byteArrayOf(-11), + "age_over_65" to byteArrayOf(-12), + "age_birth_year" to byteArrayOf(25, 7, -63), + "birth_city" to byteArrayOf(107, 75, 65, 84, 82, 73, 78, 69, 72, 79, 76, 77), + "gender" to byteArrayOf(1), + "expiry_date" to byteArrayOf( + -64, + 116, + 50, + 48, + 53, + 48, + 45, + 48, + 51, + 45, + 51, + 48, + 84, + 48, + 48, + 58, + 48, + 48, + 58, + 48, + 48, + 90 + ), +) + +val mockedMdlBasicFields: Map = mapOf( + "family_name" to byteArrayOf(105, 65, 78, 68, 69, 82, 83, 83, 79, 78), + "given_name" to byteArrayOf(99, 74, 65, 78), + "birth_place" to byteArrayOf(102, 83, 87, 69, 68, 69, 78), + "expiry_date" to byteArrayOf( + -64, + 116, + 50, + 48, + 53, + 48, + 45, + 48, + 51, + 45, + 51, + 48, + 84, + 48, + 48, + 58, + 48, + 48, + 58, + 48, + 48, + 90 + ), + "portrait" to byteArrayOf(98, 83, 69), + "driving_privileges" to byteArrayOf( + -126, + -93, + 106, + 105, + 115, + 115, + 117, + 101, + 95, + 100, + 97, + 116, + 101, + -39, + 3, + -20, + 106, + 50, + 48, + 49, + 48, + 45, + 48, + 55, + 45, + 48, + 49, + 107, + 101, + 120, + 112, + 105, + 114, + 121, + 95, + 100, + 97, + 116, + 101, + -39, + 3, + -20, + 106, + 50, + 48, + 53, + 48, + 45, + 48, + 51, + 45, + 51, + 48, + 117, + 118, + 101, + 104, + 105, + 99, + 108, + 101, + 95, + 99, + 97, + 116, + 101, + 103, + 111, + 114, + 121, + 95, + 99, + 111, + 100, + 101, + 97, + 65, + -93, + 106, + 105, + 115, + 115, + 117, + 101, + 95, + 100, + 97, + 116, + 101, + -39, + 3, + -20, + 106, + 50, + 48, + 48, + 56, + 45, + 48, + 53, + 45, + 49, + 57, + 107, + 101, + 120, + 112, + 105, + 114, + 121, + 95, + 100, + 97, + 116, + 101, + -39, + 3, + -20, + 106, + 50, + 48, + 53, + 48, + 45, + 48, + 51, + 45, + 51, + 48, + 117, + 118, + 101, + 104, + 105, + 99, + 108, + 101, + 95, + 99, + 97, + 116, + 101, + 103, + 111, + 114, + 121, + 95, + 99, + 111, + 100, + 101, + 97, + 66 + ), + + "signature_usual_mark" to byteArrayOf(98, 83, 69), + "sex" to byteArrayOf(1), +) + const val mockedPidDocType = "eu.europa.ec.eudiw.pid.1" const val mockedPidCodeName = "eu.europa.ec.eudiw.pid.1" const val mockedMdlDocType = "org.iso.18013.5.1.mDL" @@ -434,6 +657,18 @@ val mockedFullPid = Document( ) ) +val mockedPidWithBasicFields = mockedFullPid.copy( + nameSpacedData = mapOf( + mockedPidCodeName to mockedPidBasicFields + ) +) + +val mockedEmptyPid = mockedFullPid.copy( + nameSpacedData = mapOf( + mockedPidCodeName to emptyMap() + ) +) + val mockedFullMdl = Document( id = mockedId2, docType = mockedMdlDocType, @@ -446,6 +681,12 @@ val mockedFullMdl = Document( ) ) +val mockedMdlWithBasicFields = mockedFullMdl.copy( + nameSpacedData = mapOf( + mockedMdlCodeName to mockedMdlBasicFields + ) +) + val mockedMdlWithNoExpirationDate: Document = mockedFullMdl.copy( nameSpacedData = mapOf( mockedMdlCodeName to diff --git a/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt b/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt index 0268bff8..d0313d68 100644 --- a/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt +++ b/test-feature/src/main/java/eu/europa/ec/testfeature/Utils.kt @@ -19,6 +19,7 @@ package eu.europa.ec.testfeature import androidx.annotation.VisibleForTesting import eu.europa.ec.resourceslogic.R import eu.europa.ec.resourceslogic.provider.ResourceProvider +import org.mockito.ArgumentMatchers import org.mockito.kotlin.whenever private const val mockedDocUiNamePid = "National ID" @@ -26,20 +27,67 @@ private const val mockedDocUiNameMdl = "Driving License" private const val mockedDocUiNameConferenceBadge = "EUDI Conference Badge" private const val mockedDocUiNameSampleData = "Load Sample Documents" -/** - * Mock the call of [eu.europa.ec.commonfeature.model.toUiName] - */ @VisibleForTesting(otherwise = VisibleForTesting.NONE) -fun mockDocumentTypeUiToUiNameCall(resourceProvider: ResourceProvider) { - whenever(resourceProvider.getString(R.string.pid)) - .thenReturn(mockedDocUiNamePid) +object MockResourceProviderForStringCalls { + + /** + * Mock the call of [eu.europa.ec.commonfeature.model.toUiName] + */ + fun mockDocumentTypeUiToUiNameCall(resourceProvider: ResourceProvider) { + whenever(resourceProvider.getString(R.string.pid)) + .thenReturn(mockedDocUiNamePid) + + whenever(resourceProvider.getString(R.string.mdl)) + .thenReturn(mockedDocUiNameMdl) + + whenever(resourceProvider.getString(R.string.conference_badge)) + .thenReturn(mockedDocUiNameConferenceBadge) + + whenever(resourceProvider.getString(R.string.load_sample_data)) + .thenReturn(mockedDocUiNameSampleData) + } + + /** + * Mock the call of [eu.europa.ec.commonfeature.ui.document_details.transformer.DocumentDetailsTransformer.transformToUiItem] + */ + fun mockTransformToUiItemCall(resourceProvider: ResourceProvider) { + mockTransformToDocumentDetailsUiCall(resourceProvider) + } + + /** + * Mock the call of [eu.europa.ec.commonfeature.ui.document_details.transformer.transformToDocumentDetailsUi] + */ + fun mockTransformToDocumentDetailsUiCall(resourceProvider: ResourceProvider) { + whenever(resourceProvider.getString(R.string.document_details_portrait_readable_identifier)) + .thenReturn("Shown above") + + mockGetKeyValueUiCall(resourceProvider) + } + + /** + * Mock the call of [eu.europa.ec.commonfeature.util.getKeyValueUi] + */ + fun mockGetKeyValueUiCall(resourceProvider: ResourceProvider) { + whenever(resourceProvider.getReadableElementIdentifier(ArgumentMatchers.anyString())) + .then { + it.arguments.first() + } - whenever(resourceProvider.getString(R.string.mdl)) - .thenReturn(mockedDocUiNameMdl) + whenever(resourceProvider.getString(R.string.document_details_boolean_item_true_readable_value)) + .thenReturn("yes") + whenever(resourceProvider.getString(R.string.document_details_boolean_item_false_readable_value)) + .thenReturn("no") - whenever(resourceProvider.getString(R.string.conference_badge)) - .thenReturn(mockedDocUiNameConferenceBadge) + mockGetGenderValueCall(resourceProvider) + } - whenever(resourceProvider.getString(R.string.load_sample_data)) - .thenReturn(mockedDocUiNameSampleData) + /** + * Mock the call of [eu.europa.ec.commonfeature.util.getGenderValue] + */ + fun mockGetGenderValueCall(resourceProvider: ResourceProvider) { + whenever(resourceProvider.getString(R.string.request_gender_male)) + .thenReturn("male") + whenever(resourceProvider.getString(R.string.request_gender_female)) + .thenReturn("female") + } } \ No newline at end of file diff --git a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/InfoTextWithNameAndValue.kt b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/InfoTextWithNameAndValue.kt index ef8b1037..1cb4a904 100644 --- a/ui-logic/src/main/java/eu/europa/ec/uilogic/component/InfoTextWithNameAndValue.kt +++ b/ui-logic/src/main/java/eu/europa/ec/uilogic/component/InfoTextWithNameAndValue.kt @@ -51,6 +51,28 @@ class InfoTextWithNameAndValueData private constructor( val title: String, val infoValues: List?, ) { + override fun equals(other: Any?): Boolean { + return if (other !is InfoTextWithNameAndValueData) { + false + } else { + (this.title == other.title) && + (this.infoValues?.equals(other.infoValues) == true) + } + } + + override fun hashCode(): Int { + var result = title.hashCode() + result = 31 * result + (infoValues?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return buildString { + append("\nTitle = $title,\n") + append("InfoValues = $infoValues") + } + } + companion object { fun create( title: String, From 0c3919336ffc6a2d359cc20db5b614dceaf3c23a Mon Sep 17 00:00:00 2001 From: Giannis Stamatopoulos Date: Fri, 8 Mar 2024 13:04:53 +0200 Subject: [PATCH 4/4] Review corrections. --- .../document/details/DocumentDetailsScreen.kt | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt index 20e0565d..171455c5 100644 --- a/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt +++ b/issuance-feature/src/main/java/eu/europa/ec/issuancefeature/ui/document/details/DocumentDetailsScreen.kt @@ -138,7 +138,7 @@ fun DocumentDetailsScreen( sheetState = bottomSheetState ) { SheetContent( - documentTypeUiName = state.document?.documentName, + documentTypeUiName = state.document?.documentName.orEmpty(), onEventSent = { viewModel.setEvent(it) } @@ -274,25 +274,23 @@ private fun Content( @Composable private fun SheetContent( - documentTypeUiName: String?, + documentTypeUiName: String, onEventSent: (event: Event) -> Unit ) { - documentTypeUiName?.let { - DialogBottomSheet( - title = stringResource( - id = R.string.document_details_bottom_sheet_delete_title, - it - ), - message = stringResource( - id = R.string.document_details_bottom_sheet_delete_subtitle, - it - ), - positiveButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_primary_button_text), - negativeButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_secondary_button_text), - onPositiveClick = { onEventSent(Event.BottomSheet.Delete.PrimaryButtonPressed) }, - onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) } - ) - } + DialogBottomSheet( + title = stringResource( + id = R.string.document_details_bottom_sheet_delete_title, + documentTypeUiName + ), + message = stringResource( + id = R.string.document_details_bottom_sheet_delete_subtitle, + documentTypeUiName + ), + positiveButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_primary_button_text), + negativeButtonText = stringResource(id = R.string.document_details_bottom_sheet_delete_secondary_button_text), + onPositiveClick = { onEventSent(Event.BottomSheet.Delete.PrimaryButtonPressed) }, + onNegativeClick = { onEventSent(Event.BottomSheet.Delete.SecondaryButtonPressed) } + ) } @Composable