diff --git a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/DraftFields.kt b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/DraftFields.kt index 626ad6900b..c425ad4be1 100644 --- a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/DraftFields.kt +++ b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/DraftFields.kt @@ -21,12 +21,19 @@ package ch.protonmail.android.mailcomposer.domain.model data class DraftFields( val sender: SenderEmail, val subject: Subject, - val body: DraftBody + val body: DraftBody, + val recipientsTo: RecipientsTo, + val recipientsCc: RecipientsCc, + val recipientsBcc: RecipientsBcc ) { /** * Returns true if all of the fields (except sender) are blank. * Can be used to infer whether these fields should be used to store a draft or discarded. */ - fun areBlank() = subject.value.isBlank() && body.value.isBlank() + fun areBlank() = subject.value.isBlank() && + body.value.isBlank() && + recipientsTo.value.isEmpty() && + recipientsCc.value.isEmpty() && + recipientsBcc.value.isEmpty() } diff --git a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsBcc.kt b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsBcc.kt new file mode 100644 index 0000000000..08c336cb28 --- /dev/null +++ b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsBcc.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Proton Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Proton Mail. If not, see . + */ + +package ch.protonmail.android.mailcomposer.domain.model + +import ch.protonmail.android.mailmessage.domain.entity.Recipient + +@JvmInline +value class RecipientsBcc(val value: List) diff --git a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsCc.kt b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsCc.kt new file mode 100644 index 0000000000..e239e2ff70 --- /dev/null +++ b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsCc.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Proton Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Proton Mail. If not, see . + */ + +package ch.protonmail.android.mailcomposer.domain.model + +import ch.protonmail.android.mailmessage.domain.entity.Recipient + +@JvmInline +value class RecipientsCc(val value: List) diff --git a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsTo.kt b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsTo.kt new file mode 100644 index 0000000000..b1e9eee2c4 --- /dev/null +++ b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/model/RecipientsTo.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Proton Mail is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Proton Mail. If not, see . + */ + +package ch.protonmail.android.mailcomposer.domain.model + +import ch.protonmail.android.mailmessage.domain.entity.Recipient + +@JvmInline +value class RecipientsTo(val value: List) diff --git a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFields.kt b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFields.kt index c636ad5abf..c1c4fdbf31 100644 --- a/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFields.kt +++ b/mail-composer/domain/src/main/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFields.kt @@ -29,7 +29,8 @@ import javax.inject.Inject class StoreDraftWithAllFields @Inject constructor( private val storeDraftWithSubject: StoreDraftWithSubject, - private val storeDraftWithBody: StoreDraftWithBody + private val storeDraftWithBody: StoreDraftWithBody, + private val storeDraftWithRecipients: StoreDraftWithRecipients ) { suspend operator fun invoke( @@ -40,6 +41,14 @@ class StoreDraftWithAllFields @Inject constructor( withContext(NonCancellable) { storeDraftWithBody(draftMessageId, fields.body, fields.sender, userId).logError(draftMessageId) storeDraftWithSubject(userId, draftMessageId, fields.sender, fields.subject).logError(draftMessageId) + storeDraftWithRecipients( + userId, + draftMessageId, + fields.sender, + fields.recipientsTo.value, + fields.recipientsCc.value, + fields.recipientsBcc.value + ).logError(draftMessageId) } } diff --git a/mail-composer/domain/src/test/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFieldsTest.kt b/mail-composer/domain/src/test/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFieldsTest.kt index 4626061f7c..5949dc4d59 100644 --- a/mail-composer/domain/src/test/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFieldsTest.kt +++ b/mail-composer/domain/src/test/kotlin/ch/protonmail/android/mailcomposer/domain/usecase/StoreDraftWithAllFieldsTest.kt @@ -24,10 +24,14 @@ import ch.protonmail.android.mailcommon.domain.sample.UserAddressSample import ch.protonmail.android.mailcommon.domain.sample.UserIdSample import ch.protonmail.android.mailcomposer.domain.model.DraftBody import ch.protonmail.android.mailcomposer.domain.model.DraftFields +import ch.protonmail.android.mailcomposer.domain.model.RecipientsBcc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsCc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsTo import ch.protonmail.android.mailcomposer.domain.model.SenderEmail import ch.protonmail.android.mailcomposer.domain.model.Subject import ch.protonmail.android.mailmessage.domain.entity.MessageId import ch.protonmail.android.mailmessage.domain.sample.MessageIdSample +import ch.protonmail.android.mailmessage.domain.sample.RecipientSample import ch.protonmail.android.test.utils.rule.LoggingTestRule import io.mockk.coEvery import io.mockk.coVerify @@ -44,10 +48,12 @@ class StoreDraftWithAllFieldsTest { private val storeDraftWithSubjectMock = mockk() private val storeDraftWithBodyMock = mockk() + private val storeDraftWithRecipientsMock = mockk() private val storeDraftWithAllFields = StoreDraftWithAllFields( storeDraftWithSubjectMock, - storeDraftWithBodyMock + storeDraftWithBodyMock, + storeDraftWithRecipientsMock ) @Test @@ -58,9 +64,25 @@ class StoreDraftWithAllFieldsTest { val senderEmail = SenderEmail(UserAddressSample.PrimaryAddress.email) val subject = Subject("Subject of this email") val plaintextDraftBody = DraftBody("I am plaintext") - val draftFields = DraftFields(senderEmail, subject, plaintextDraftBody) + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val draftFields = DraftFields( + senderEmail, + subject, + plaintextDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) expectStoreDraftBodySucceeds(draftMessageId, plaintextDraftBody, senderEmail, userId) expectStoreDraftSubjectSucceeds(userId, draftMessageId, senderEmail, subject) + expectStoreDraftRecipientsSucceeds( + userId, + draftMessageId, + senderEmail, + Triple(recipientsTo, recipientsCc, recipientsBcc) + ) // When storeDraftWithAllFields(userId, draftMessageId, draftFields) @@ -68,6 +90,16 @@ class StoreDraftWithAllFieldsTest { // Then coVerify { storeDraftWithBodyMock(draftMessageId, plaintextDraftBody, senderEmail, userId) } coVerify { storeDraftWithSubjectMock(userId, draftMessageId, senderEmail, subject) } + coVerify { + storeDraftWithRecipientsMock( + userId, + draftMessageId, + senderEmail, + recipientsTo.value, + recipientsCc.value, + recipientsBcc.value + ) + } } @Test @@ -78,10 +110,26 @@ class StoreDraftWithAllFieldsTest { val senderEmail = SenderEmail(UserAddressSample.PrimaryAddress.email) val subject = Subject("Subject of this email") val plaintextDraftBody = DraftBody("I am plaintext") - val draftFields = DraftFields(senderEmail, subject, plaintextDraftBody) + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val draftFields = DraftFields( + senderEmail, + subject, + plaintextDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) val expectedError = StoreDraftWithBodyError.DraftReadError expectStoreDraftBodyFails(draftMessageId, plaintextDraftBody, senderEmail, userId) { expectedError } expectStoreDraftSubjectSucceeds(userId, draftMessageId, senderEmail, subject) + expectStoreDraftRecipientsSucceeds( + userId, + draftMessageId, + senderEmail, + Triple(recipientsTo, recipientsCc, recipientsBcc) + ) // When storeDraftWithAllFields(userId, draftMessageId, draftFields) @@ -99,10 +147,63 @@ class StoreDraftWithAllFieldsTest { val senderEmail = SenderEmail(UserAddressSample.PrimaryAddress.email) val subject = Subject("Subject of this email") val plaintextDraftBody = DraftBody("I am plaintext") - val draftFields = DraftFields(senderEmail, subject, plaintextDraftBody) + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val draftFields = DraftFields( + senderEmail, + subject, + plaintextDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) val expectedError = StoreDraftWithSubject.Error.DraftSaveError expectStoreDraftBodySucceeds(draftMessageId, plaintextDraftBody, senderEmail, userId) expectStoreDraftSubjectFails(draftMessageId, senderEmail, userId, subject) { expectedError } + expectStoreDraftRecipientsSucceeds( + userId, + draftMessageId, + senderEmail, + Triple(recipientsTo, recipientsCc, recipientsBcc) + ) + + // When + storeDraftWithAllFields(userId, draftMessageId, draftFields) + + // Then + val expectedLog = "Storing all draft fields failed due to $expectedError. \n Draft MessageId = $draftMessageId" + loggingTestRule.assertErrorLogged(expectedLog) + } + + @Test + fun `logs error when store draft with recipients fails`() = runTest { + // Given + val userId = UserIdSample.Primary + val draftMessageId = MessageIdSample.build() + val senderEmail = SenderEmail(UserAddressSample.PrimaryAddress.email) + val subject = Subject("Subject of this email") + val plaintextDraftBody = DraftBody("I am plaintext") + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val draftFields = DraftFields( + senderEmail, + subject, + plaintextDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) + val expectedError = StoreDraftWithRecipients.Error.DraftSaveError + expectStoreDraftBodySucceeds(draftMessageId, plaintextDraftBody, senderEmail, userId) + expectStoreDraftSubjectSucceeds(userId, draftMessageId, senderEmail, subject) + expectStoreDraftRecipientsFails( + draftMessageId, + senderEmail, + userId, + Triple(recipientsTo, recipientsCc, recipientsBcc) + ) { expectedError } // When storeDraftWithAllFields(userId, draftMessageId, draftFields) @@ -178,4 +279,41 @@ class StoreDraftWithAllFieldsTest { } returns it.left() } + private fun expectStoreDraftRecipientsSucceeds( + expectedUserId: UserId, + expectedMessageId: MessageId, + expectedSenderEmail: SenderEmail, + recipients: Triple + ) { + coEvery { + storeDraftWithRecipientsMock( + expectedUserId, + expectedMessageId, + expectedSenderEmail, + recipients.first.value, + recipients.second.value, + recipients.third.value + ) + } returns Unit.right() + } + + private fun expectStoreDraftRecipientsFails( + expectedMessageId: MessageId, + expectedSenderEmail: SenderEmail, + expectedUserId: UserId, + recipients: Triple, + error: () -> StoreDraftWithRecipients.Error + ) = error().also { + coEvery { + storeDraftWithRecipientsMock( + expectedUserId, + expectedMessageId, + expectedSenderEmail, + recipients.first.value, + recipients.second.value, + recipients.third.value + ) + } returns it.left() + } + } diff --git a/mail-composer/presentation/src/main/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModel.kt b/mail-composer/presentation/src/main/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModel.kt index 05982492d4..0e043b89f9 100644 --- a/mail-composer/presentation/src/main/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModel.kt +++ b/mail-composer/presentation/src/main/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModel.kt @@ -24,6 +24,9 @@ import arrow.core.getOrElse import ch.protonmail.android.mailcommon.domain.usecase.ObservePrimaryUserId import ch.protonmail.android.mailcomposer.domain.model.DraftBody import ch.protonmail.android.mailcomposer.domain.model.DraftFields +import ch.protonmail.android.mailcomposer.domain.model.RecipientsBcc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsCc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsTo import ch.protonmail.android.mailcomposer.domain.model.SenderEmail import ch.protonmail.android.mailcomposer.domain.model.Subject import ch.protonmail.android.mailcomposer.domain.usecase.GetComposerSenderAddresses @@ -110,7 +113,14 @@ class ComposerViewModel @Inject constructor( fun validateEmailAddress(emailAddress: String): Boolean = isValidEmailAddress(emailAddress) private suspend fun onCloseComposer(action: ComposerAction.OnCloseComposer): ComposerOperation { - val fields = DraftFields(currentSenderEmail(), currentSubject(), currentDraftBody()) + val fields = DraftFields( + currentSenderEmail(), + currentSubject(), + currentDraftBody(), + currentValidRecipientsTo(), + currentValidRecipientsCc(), + currentValidRecipientsBcc() + ) return when { fields.areBlank() -> action else -> { @@ -152,6 +162,24 @@ class ComposerViewModel @Inject constructor( private fun currentSenderEmail() = SenderEmail(state.value.fields.sender.email) + private suspend fun currentValidRecipientsTo() = RecipientsTo( + state.value.fields.to.filterIsInstance().map { + participantMapper.recipientUiModelToParticipant(it, contactsOrEmpty()) + } + ) + + private suspend fun currentValidRecipientsCc() = RecipientsCc( + state.value.fields.cc.filterIsInstance().map { + participantMapper.recipientUiModelToParticipant(it, contactsOrEmpty()) + } + ) + + private suspend fun currentValidRecipientsBcc() = RecipientsBcc( + state.value.fields.bcc.filterIsInstance().map { + participantMapper.recipientUiModelToParticipant(it, contactsOrEmpty()) + } + ) + private suspend fun contactsOrEmpty() = getContacts(primaryUserId()).getOrElse { emptyList() } private suspend fun onChangeSender() = getComposerSenderAddresses().fold( diff --git a/mail-composer/presentation/src/test/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModelTest.kt b/mail-composer/presentation/src/test/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModelTest.kt index 94c6c52398..b8827ea852 100644 --- a/mail-composer/presentation/src/test/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModelTest.kt +++ b/mail-composer/presentation/src/test/kotlin/ch/protonmail/android/mailcomposer/presentation/viewmodel/ComposerViewModelTest.kt @@ -28,6 +28,9 @@ import ch.protonmail.android.mailcommon.presentation.Effect import ch.protonmail.android.mailcommon.presentation.model.TextUiModel import ch.protonmail.android.mailcomposer.domain.model.DraftBody import ch.protonmail.android.mailcomposer.domain.model.DraftFields +import ch.protonmail.android.mailcomposer.domain.model.RecipientsBcc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsCc +import ch.protonmail.android.mailcomposer.domain.model.RecipientsTo import ch.protonmail.android.mailcomposer.domain.model.SenderEmail import ch.protonmail.android.mailcomposer.domain.model.Subject import ch.protonmail.android.mailcomposer.domain.usecase.GetComposerSenderAddresses @@ -51,6 +54,7 @@ import ch.protonmail.android.mailcontact.domain.usecase.GetContacts import ch.protonmail.android.mailmessage.domain.entity.MessageId import ch.protonmail.android.mailmessage.domain.entity.Recipient import ch.protonmail.android.mailmessage.domain.sample.MessageIdSample +import ch.protonmail.android.mailmessage.domain.sample.RecipientSample import ch.protonmail.android.test.utils.rule.LoggingTestRule import ch.protonmail.android.test.utils.rule.MainDispatcherRule import ch.protonmail.android.testdata.contact.ContactSample @@ -63,6 +67,7 @@ import io.mockk.mockkObject import io.mockk.unmockkObject import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest +import me.proton.core.contact.domain.entity.Contact import me.proton.core.domain.entity.UserId import me.proton.core.user.domain.entity.UserAddress import kotlin.test.AfterTest @@ -314,11 +319,28 @@ class ComposerViewModelTest { val expectedUserId = expectedUserId { UserIdSample.Primary } val expectedDraftBody = DraftBody("I am plaintext") expectedPrimaryAddress(expectedUserId) { UserAddressSample.PrimaryAddress } - val expectedFields = DraftFields(expectedSenderEmail, expectedSubject, expectedDraftBody) + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val expectedFields = DraftFields( + expectedSenderEmail, + expectedSubject, + expectedDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) + mockParticipantMapper() expectStoreAllDraftFieldsSucceeds(expectedUserId, expectedMessageId, expectedFields) // Change internal state of the View Model to simulate the existence of all fields before closing the composer - expectedViewModelInitialState(expectedMessageId, expectedSenderEmail, expectedSubject, expectedDraftBody) + expectedViewModelInitialState( + expectedMessageId, + expectedSenderEmail, + expectedSubject, + expectedDraftBody, + Triple(recipientsTo, recipientsCc, recipientsBcc) + ) // When viewModel.submit(ComposerAction.OnCloseComposer) @@ -352,11 +374,27 @@ class ComposerViewModelTest { val expectedMessageId = expectedMessageId { MessageIdSample.EmptyDraft } val expectedUserId = expectedUserId { UserIdSample.Primary } expectedPrimaryAddress(expectedUserId) { UserAddressSample.PrimaryAddress } - val expectedFields = DraftFields(expectedSenderEmail, expectedSubject, expectedDraftBody) + val recipientsTo = RecipientsTo(listOf(RecipientSample.John)) + val recipientsCc = RecipientsCc(listOf(RecipientSample.John)) + val recipientsBcc = RecipientsBcc(listOf(RecipientSample.John)) + val expectedFields = DraftFields( + expectedSenderEmail, + expectedSubject, + expectedDraftBody, + recipientsTo, + recipientsCc, + recipientsBcc + ) + mockParticipantMapper() expectStoreAllDraftFieldsSucceeds(expectedUserId, expectedMessageId, expectedFields) // Change internal state of the View Model to simulate the existence of all fields before closing the composer - expectedViewModelInitialState(expectedMessageId, expectedSenderEmail, expectedSubject) + expectedViewModelInitialState( + expectedMessageId, + expectedSenderEmail, + expectedSubject, + recipients = Triple(recipientsTo, recipientsCc, recipientsBcc) + ) // When viewModel.submit(ComposerAction.OnCloseComposer) @@ -635,15 +673,20 @@ class ComposerViewModelTest { messageId: MessageId, senderEmail: SenderEmail = SenderEmail(""), subject: Subject = Subject(""), - draftBody: DraftBody = DraftBody("") + draftBody: DraftBody = DraftBody(""), + recipients: Triple = Triple( + RecipientsTo(emptyList()), + RecipientsCc(emptyList()), + RecipientsBcc(emptyList()) + ) ) { val expected = ComposerDraftState( fields = ComposerFields( messageId, SenderUiModel(senderEmail.value), - emptyList(), - emptyList(), - emptyList(), + recipients.first.value.map { RecipientUiModel.Valid(it.address) }, + recipients.second.value.map { RecipientUiModel.Valid(it.address) }, + recipients.third.value.map { RecipientUiModel.Valid(it.address) }, subject.value, draftBody.value ), @@ -804,15 +847,26 @@ class ComposerViewModelTest { } returns Unit } - private fun mockParticipantMapper() { + private fun expectContacts(): List { val expectedContacts = listOf(ContactSample.Doe, ContactSample.John) coEvery { getContactsMock.invoke(UserIdSample.Primary) } returns expectedContacts.right() + return expectedContacts + } + + private fun mockParticipantMapper() { + val expectedContacts = expectContacts() every { participantMapperMock.recipientUiModelToParticipant( RecipientUiModel.Valid("valid@email.com"), expectedContacts ) } returns Recipient("valid@email.com", "Valid Email", false) + every { + participantMapperMock.recipientUiModelToParticipant( + RecipientUiModel.Valid(RecipientSample.John.address), + any() + ) + } returns Recipient(RecipientSample.John.address, RecipientSample.John.name, false) } companion object TestData {