diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt index 55b95459ee73e9658f413de65d6ebbf0c1b5f1bd..d46a8e8d690bca0a26585b0e89132d0eae563f26 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt @@ -60,15 +60,19 @@ object DiaryData { ) val HIGH_RISK = ListItem.Risk( - R.string.contact_diary_risk_body, - R.string.contact_diary_high_risk_title, - R.drawable.ic_high_risk_alert + title = R.string.contact_diary_high_risk_title, + body = R.string.contact_diary_risk_body, + bodyExtended = R.string.contact_diary_risk_body_extended, + drawableId = R.drawable.ic_high_risk_alert ) + val HIGH_RISK_DUE_LOW_RISK_ENCOUNTERS = HIGH_RISK.copy(body = R.string.contact_diary_risk_body_high_risk_due_to_low_risk_encounters) + val LOW_RISK = ListItem.Risk( - R.string.contact_diary_risk_body, - R.string.contact_diary_low_risk_title, - R.drawable.ic_low_risk_alert + title = R.string.contact_diary_low_risk_title, + body = R.string.contact_diary_risk_body, + bodyExtended = R.string.contact_diary_risk_body_extended, + drawableId = R.drawable.ic_low_risk_alert ) val LOCATIONS: List<DiaryLocationListItem> = listOf( diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt index 6f2fb1680365478578ca595b9369fdaaea5ddd71..0ef69d98c02304ce2c1000ea334206153f87d7b8 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt @@ -323,10 +323,14 @@ class MainActivityTest : BaseUITest() { MutableLiveData( (0 until ContactDiaryOverviewViewModel.DAY_COUNT) .map { LocalDate.now().minusDays(it) } - .map { - ListItem(it).apply { + .mapIndexed { index, localDate -> + ListItem(localDate).apply { data.addAll(DiaryData.DATA_ITEMS) - risk = if (it.dayOfYear % 2 == 0) DiaryData.HIGH_RISK else DiaryData.LOW_RISK + risk = when (index % 3) { + 0 -> DiaryData.HIGH_RISK + 1 -> DiaryData.HIGH_RISK_DUE_LOW_RISK_ENCOUNTERS + else -> DiaryData.LOW_RISK + } } } ) diff --git a/Corona-Warn-App/src/deviceForTesters/assets/exposure-windows-increased-risk-due-to-low-risk-encounter-random.json b/Corona-Warn-App/src/deviceForTesters/assets/exposure-windows-increased-risk-due-to-low-risk-encounter-random.json new file mode 100644 index 0000000000000000000000000000000000000000..0f3fcf61f725b4520c036609eeee1b7746b4d6e7 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/assets/exposure-windows-increased-risk-due-to-low-risk-encounter-random.json @@ -0,0 +1,110 @@ +[ + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 26, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 26, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 1, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 27, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 27, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 25, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 26, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 26, + "secondsSinceLastScan": 300 + } + ] + }, + { + "ageInDays": 2, + "reportType": 3, + "infectiousness": 1, + "calibrationConfidence": 0, + "scanInstances": [ + { + "minAttenuation": 30, + "typicalAttenuation": 27, + "secondsSinceLastScan": 300 + }, + { + "minAttenuation": 30, + "typicalAttenuation": 27, + "secondsSinceLastScan": 300 + } + ] + } +] \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/FakeExposureWindowProvider.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/FakeExposureWindowProvider.kt index ce213e8e03432873ea37ac457889cc65c92da657..12f7ef5474446268b0f44e7df6f3dae5f6a32551 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/FakeExposureWindowProvider.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/nearby/modules/exposurewindow/FakeExposureWindowProvider.kt @@ -24,6 +24,7 @@ class FakeExposureWindowProvider @Inject constructor( fun getExposureWindows(testSettings: FakeExposureWindowTypes): List<ExposureWindow> { val jsonInput = when (testSettings) { FakeExposureWindowTypes.INCREASED_RISK_DEFAULT -> "exposure-windows-increased-risk-random.json" + FakeExposureWindowTypes.INCREASED_RISK_DUE_LOW_RISK_ENCOUNTER_DEFAULT -> "exposure-windows-increased-risk-due-to-low-risk-encounter-random.json" FakeExposureWindowTypes.LOW_RISK_DEFAULT -> "exposure-windows-lowrisk-random.json" else -> throw NotImplementedError() }.let { context.assets.open(it) }.readBytes().toString(Charsets.UTF_8) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt index f2c304ca1f3daa15c2c6b0c0bf004a4fa9677fc3..bf01a2f7b9daa8131a46256a34de11fd8ba342a4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt @@ -85,29 +85,33 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( data.addLocationVisitsForDate(locationVisitList, date) risk = riskLevelPerDateList .firstOrNull { riskLevelPerDate -> riskLevelPerDate.day == it } - ?.toRisk(data.isEmpty()) + ?.toRisk(data.isNotEmpty()) } } } - private fun AggregatedRiskPerDateResult.toRisk(noLocationOrPerson: Boolean): ListItem.Risk { + private fun AggregatedRiskPerDateResult.toRisk(locationOrPerson: Boolean): ListItem.Risk { @StringRes val title: Int + @StringRes var body: Int = R.string.contact_diary_risk_body @DrawableRes val drawableId: Int - @StringRes val body: Int = when (noLocationOrPerson) { - true -> R.string.contact_diary_risk_body - false -> R.string.contact_diary_risk_body_extended + @StringRes val bodyExtend: Int? = when (locationOrPerson) { + true -> R.string.contact_diary_risk_body_extended + false -> null } if (this.riskLevel == RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH) { title = R.string.contact_diary_high_risk_title drawableId = R.drawable.ic_high_risk_alert + if (minimumDistinctEncountersWithHighRisk == 0) { + body = R.string.contact_diary_risk_body_high_risk_due_to_low_risk_encounters + } } else { title = R.string.contact_diary_low_risk_title drawableId = R.drawable.ic_low_risk_alert } - return ListItem.Risk(title, body, drawableId) + return ListItem.Risk(title, body, bodyExtend, drawableId) } private fun MutableList<ListItem.Data>.addPersonEncountersForDate( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt index f8b2782ec0aabe398370eaa1ecc65f03ac572190..e58c708ef1b935e6a1a3e0f091cedec570a230cf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ContactDiaryOverviewAdapter.kt @@ -57,8 +57,15 @@ class ContactDiaryOverviewAdapter( item.risk?.let { this.contactDiaryOverviewRiskItem.isGone = false this.contactDiaryOverviewItemRiskTitle.text = context.getString(it.title) - this.contactDiaryOverviewItemRiskBody.text = context.getString(it.body) this.contactDiaryOverviewRiskItemImage.setImageResource(it.drawableId) + + val sb = StringBuilder().append(context.getString(it.body)) + + it.bodyExtended?.let { extend -> + sb.appendLine().append(context.getString(extend)) + } + + this.contactDiaryOverviewItemRiskBody.text = sb } ?: run { this.contactDiaryOverviewRiskItem.isGone = true } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ListItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ListItem.kt index 525d988f570055861e91369b66af1d1258063993..f155c87bccc2d84c968fdc4f45504ef4e4e3060c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ListItem.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/adapter/ListItem.kt @@ -23,6 +23,7 @@ data class ListItem( data class Risk( @StringRes val title: Int, @StringRes val body: Int, + @StringRes val bodyExtended: Int? = null, @DrawableRes val drawableId: Int ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt index c0099d3a3f1550da855c969ff4c5e8ce6b0a289e..330028b103a14b22ba997670bb6a439c2fc85db9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TestSettings.kt @@ -47,6 +47,9 @@ class TestSettings @Inject constructor( @SerializedName("INCREASED_RISK_DEFAULT") INCREASED_RISK_DEFAULT, + @SerializedName("INCREASED_RISK_DUE_LOW_RISK_ENCOUNTER_DEFAULT") + INCREASED_RISK_DUE_LOW_RISK_ENCOUNTER_DEFAULT, + @SerializedName("LOW_RISK_DEFAULT") LOW_RISK_DEFAULT } diff --git a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml index 2bf855a09b9124fc7a354ccbe4f0528bca6edf11..72abf54c41ffaef59f4815031746ad4b7ac6217e 100644 --- a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml @@ -73,8 +73,10 @@ <string name="contact_diary_low_risk_title">"Niedriges Risiko"</string> <!-- XTXT: Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body">"aufgrund der von der App ausgewerteten Begegnungen."</string> + <!-- XTXT: Body for contact diary overview screen risk information when high risk due to low risk encounters--> + <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"aufgrund von mehreren Begegnungen mit niedrigem Risiko."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> - <string name="contact_diary_risk_body_extended">"aufgrund der von der App ausgewerteten Begegnungen. Diese müssen nicht in Zusammenhang mit den von Ihnen erfassten Personen und Orten stehen."</string> + <string name="contact_diary_risk_body_extended">"Diese müssen nicht in Zusammenhang mit den von Ihnen erfassten Personen und Orten stehen."</string> <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml index b9c30d5c6f88ffd376e71d628ed4a36df1405e99..1db1884f3cd01ac69d8cc85dd4609bfcda3d59b3 100644 --- a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml @@ -73,8 +73,10 @@ <string name="contact_diary_low_risk_title">"Low Risk"</string> <!-- XTXT: Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body">"based on the encounters evaluated by the app."</string> + <!-- XTXT: Body for contact diary overview screen risk information when high risk due to low risk encounters--> + <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"based on several encounters with low risk."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> - <string name="contact_diary_risk_body_extended">"based on the encounters evaluated by the app. They are not necessarily related to the people and places you have recorded."</string> + <string name="contact_diary_risk_body_extended">"They are not necessarily related to the people and places you have recorded."</string> <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..9de044a53bccdd9bd70e39e2dd9d60c66b6f2c62 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt @@ -0,0 +1,296 @@ +package de.rki.coronawarnapp.contactdiary.ui.overview + +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryPersonEncounter +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.ListItem +import de.rki.coronawarnapp.risk.result.AggregatedRiskPerDateResult +import de.rki.coronawarnapp.risk.storage.RiskLevelStorage +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass +import de.rki.coronawarnapp.task.TaskController +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.beInstanceOf +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.runs +import io.mockk.verify +import kotlinx.coroutines.flow.flowOf +import org.joda.time.DateTimeZone +import org.joda.time.LocalDate +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension +import testhelpers.extensions.getOrAwaitValue + +@ExtendWith(InstantExecutorExtension::class) +open class ContactDiaryOverviewViewModelTest { + + @MockK lateinit var taskController: TaskController + @MockK lateinit var contactDiaryRepository: ContactDiaryRepository + @MockK lateinit var riskLevelStorage: RiskLevelStorage + private val testDispatcherProvider = TestDispatcherProvider() + private val date = LocalDate.now() + private val dateMillis = date.toDateTimeAtStartOfDay(DateTimeZone.UTC).millis + + @BeforeEach + fun refresh() { + MockKAnnotations.init(this) + + every { taskController.submit(any()) } just runs + every { contactDiaryRepository.locationVisits } returns flowOf(emptyList()) + every { contactDiaryRepository.personEncounters } returns flowOf(emptyList()) + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(emptyList()) + } + + private val person = DefaultContactDiaryPerson(123, "Romeo") + private val location = DefaultContactDiaryLocation(124, "Rewe") + private val personEncounter = DefaultContactDiaryPersonEncounter(125, date, person) + private val locationVisit = DefaultContactDiaryLocationVisit(126, date, location) + + private val aggregatedRiskPerDateResultLowRisk = AggregatedRiskPerDateResult( + dateMillisSinceEpoch = dateMillis, + riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.LOW, + minimumDistinctEncountersWithLowRisk = 1, + minimumDistinctEncountersWithHighRisk = 0 + ) + + private val aggregatedRiskPerDateResultLowRiskDueToHighRiskEncounter = AggregatedRiskPerDateResult( + dateMillisSinceEpoch = dateMillis, + riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, + minimumDistinctEncountersWithLowRisk = 0, + minimumDistinctEncountersWithHighRisk = 1 + ) + + private val aggregatedRiskPerDateResultLowRiskDueToLowRiskEncounter = AggregatedRiskPerDateResult( + dateMillisSinceEpoch = dateMillis, + riskLevel = RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel.HIGH, + minimumDistinctEncountersWithLowRisk = 10, + minimumDistinctEncountersWithHighRisk = 0 + ) + + fun createInstance(): ContactDiaryOverviewViewModel = ContactDiaryOverviewViewModel( + taskController = taskController, + dispatcherProvider = testDispatcherProvider, + contactDiaryRepository = contactDiaryRepository, + riskLevelStorage = riskLevelStorage + ) + + @Test + fun `submit clean task on init`() { + createInstance() + + verify(exactly = 1) { taskController.submit(any()) } + } + + @Test + fun `overview list lists all days as expected`() { + with(createInstance().listItems.getOrAwaitValue()) { + size shouldBe ContactDiaryOverviewViewModel.DAY_COUNT + + var days = 0 + forEach { it.date shouldBe date.minusDays(days++) } + } + } + + @Test + fun `navigate back to main activity`() { + with(createInstance()) { + onBackButtonPress() + routeToScreen.getOrAwaitValue() shouldBe ContactDiaryOverviewNavigationEvents.NavigateToMainActivity + } + } + + @Test + fun `navigate to day fragment with correct day`() { + val listItem = ListItem(date) + + with(createInstance()) { + onItemPress(listItem) + val navigationEvent = routeToScreen.getOrAwaitValue() + navigationEvent should beInstanceOf(ContactDiaryOverviewNavigationEvents.NavigateToContactDiaryDayFragment::class) + navigationEvent as ContactDiaryOverviewNavigationEvents.NavigateToContactDiaryDayFragment + navigationEvent.localDateString shouldBe ContactDiaryOverviewNavigationEvents.NavigateToContactDiaryDayFragment( + listItem.date + ).localDateString + } + } + + @Test + fun `low risk with person and location`() { + every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) + every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = true, + hasLocation = true + ) + + risk!!.validate( + highRisk = false, + dueToLowEncounters = false, + hasPersonOrLocation = true + ) + } + } + + @Test + fun `low risk without person or location`() { + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = false, + hasLocation = false + ) + + risk!!.validate( + highRisk = false, + dueToLowEncounters = false, + hasPersonOrLocation = false + ) + } + } + + @Test + fun `high risk due to high risk encounter with person and location`() { + every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) + every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + listOf( + aggregatedRiskPerDateResultLowRiskDueToHighRiskEncounter + ) + ) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = true, + hasLocation = true + ) + + risk!!.validate( + highRisk = true, + dueToLowEncounters = false, + hasPersonOrLocation = true + ) + } + } + + @Test + fun `high risk due to high risk encounter without person or location`() { + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + listOf( + aggregatedRiskPerDateResultLowRiskDueToHighRiskEncounter + ) + ) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = false, + hasLocation = false + ) + + risk!!.validate( + highRisk = true, + dueToLowEncounters = false, + hasPersonOrLocation = false + ) + } + } + + @Test + fun `high risk due to low risk encounter with person and location`() { + every { contactDiaryRepository.personEncounters } returns flowOf(listOf(personEncounter)) + every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationVisit)) + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + listOf( + aggregatedRiskPerDateResultLowRiskDueToLowRiskEncounter + ) + ) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = true, + hasLocation = true + ) + + risk!!.validate( + highRisk = true, + dueToLowEncounters = true, + hasPersonOrLocation = true + ) + } + } + + @Test + fun `high risk due to low risk encounter without person or location`() { + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf( + listOf( + aggregatedRiskPerDateResultLowRiskDueToLowRiskEncounter + ) + ) + + with(createInstance().listItems.getOrAwaitValue().first { it.date == date }) { + data.validate( + hasPerson = false, + hasLocation = false + ) + + risk!!.validate( + highRisk = true, + dueToLowEncounters = true, + hasPersonOrLocation = false + ) + } + } + + private fun List<ListItem.Data>.validate(hasPerson: Boolean, hasLocation: Boolean) { + var count = 0 + if (hasPerson) count++ + if (hasLocation) count++ + + size shouldBe count + forEach { + when (it.type) { + ListItem.Type.PERSON -> { + it.drawableId shouldBe R.drawable.ic_contact_diary_person_item + } + ListItem.Type.LOCATION -> { + it.drawableId shouldBe R.drawable.ic_contact_diary_location_item + } + } + } + } + + private fun ListItem.Risk.validate(highRisk: Boolean, dueToLowEncounters: Boolean, hasPersonOrLocation: Boolean) { + when (highRisk) { + true -> { + title shouldBe R.string.contact_diary_high_risk_title + drawableId shouldBe R.drawable.ic_high_risk_alert + body shouldBe when (dueToLowEncounters) { + true -> R.string.contact_diary_risk_body_high_risk_due_to_low_risk_encounters + false -> R.string.contact_diary_risk_body + } + } + false -> { + title shouldBe R.string.contact_diary_low_risk_title + body shouldBe R.string.contact_diary_risk_body + drawableId shouldBe R.drawable.ic_low_risk_alert + } + } + + bodyExtended shouldBe when (hasPersonOrLocation) { + true -> R.string.contact_diary_risk_body_extended + false -> null + } + } +}