diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1440553f485..2f8b6c67126 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,6 +200,7 @@ jobs: quest-tests: runs-on: ubuntu-latest + timeout-minutes: 145 strategy: matrix: api-level: [34] diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 837b0237748..56097d4f23e 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -38,7 +38,6 @@ apply(from = "mapbox.gradle.kts") allprojects { repositories { - gradlePluginPortal() mavenLocal() google() mavenCentral() diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt index c0c40077598..9c030a7308a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/CoreModule.kt @@ -109,11 +109,6 @@ class CoreModule { fun provideApplicationManager(@ApplicationContext context: Context): AccountManager = AccountManager.get(context) - @Singleton - @Provides - fun provideKnowledgeManager(@ApplicationContext context: Context): KnowledgeManager = - KnowledgeManager.create(context) - @Singleton @Provides fun provideFhirContext(): FhirContext = FhirContext.forR4Cached()!! @Singleton diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/di/KnowledgeManagerModule.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/KnowledgeManagerModule.kt new file mode 100644 index 00000000000..cade05f7063 --- /dev/null +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/di/KnowledgeManagerModule.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.di + +import android.content.Context +import com.google.android.fhir.knowledge.KnowledgeManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +class KnowledgeManagerModule { + + @Singleton + @Provides + fun provideKnowledgeManager(@ApplicationContext context: Context): KnowledgeManager = + KnowledgeManager.create(context) +} diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeFhirEngineModule.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeFhirEngineModule.kt index b266df616a1..83a3623581b 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeFhirEngineModule.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeFhirEngineModule.kt @@ -25,6 +25,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.hilt.testing.TestInstallIn import io.mockk.spyk +import javax.inject.Singleton import org.smartregister.fhircore.engine.di.FhirEngineModule @Module @@ -32,6 +33,7 @@ import org.smartregister.fhircore.engine.di.FhirEngineModule class FakeFhirEngineModule { @Provides + @Singleton fun provideFhirEngine(@ApplicationContext context: Context): FhirEngine { return spyk(FhirEngineProvider.getInstance(context)) } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeKnowledgeManagerModule.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeKnowledgeManagerModule.kt new file mode 100644 index 00000000000..f7633b01907 --- /dev/null +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/app/di/module/FakeKnowledgeManagerModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.engine.app.di.module + +import android.content.Context +import com.google.android.fhir.knowledge.KnowledgeManager +import dagger.Module +import dagger.Provides +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import javax.inject.Singleton +import org.smartregister.fhircore.engine.di.KnowledgeManagerModule + +@Module +@TestInstallIn(components = [SingletonComponent::class], replaces = [KnowledgeManagerModule::class]) +class FakeKnowledgeManagerModule { + + @Provides + @Singleton + fun provideKnowledgeManager(@ApplicationContext context: Context): KnowledgeManager = + KnowledgeManager.create(context, inMemory = true) +} diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt index 5c5e771401a..c9532fc0edf 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/task/FhirCarePlanGeneratorTest.kt @@ -162,8 +162,9 @@ class FhirCarePlanGeneratorTest : RobolectricTest() { @Inject lateinit var contentCache: ContentCache + @Inject lateinit var knowledgeManager: KnowledgeManager + private val context: Context = ApplicationProvider.getApplicationContext() - private val knowledgeManager = KnowledgeManager.create(context) private val fhirContext: FhirContext = FhirContext.forCached(FhirVersionEnum.R4) private lateinit var defaultRepository: DefaultRepository diff --git a/android/quest/build.gradle.kts b/android/quest/build.gradle.kts index 257c598333f..c718210c1ea 100644 --- a/android/quest/build.gradle.kts +++ b/android/quest/build.gradle.kts @@ -446,25 +446,9 @@ androidComponents { } tasks.withType { - testLogging { events = setOf(TestLogEvent.FAILED) } + testLogging { events = setOf(TestLogEvent.FAILED, TestLogEvent.STANDARD_ERROR) } minHeapSize = "4608m" maxHeapSize = "4608m" - addTestListener( - object : TestListener { - override fun beforeSuite(p0: TestDescriptor?) {} - - override fun afterSuite(p0: TestDescriptor?, p1: TestResult?) {} - - override fun beforeTest(p0: TestDescriptor?) { - logger.lifecycle("Running test: $p0") - } - - override fun afterTest(p0: TestDescriptor?, p1: TestResult?) { - logger.lifecycle("Done executing: $p0") - } - }, - ) - // maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).takeIf { it > 0 } ?: 1 configure { isIncludeNoLocationClasses = true } } diff --git a/android/quest/src/main/assets/configs/app/application_config.json b/android/quest/src/main/assets/configs/app/application_config.json index a4af956619e..c518927dca0 100644 --- a/android/quest/src/main/assets/configs/app/application_config.json +++ b/android/quest/src/main/assets/configs/app/application_config.json @@ -83,8 +83,6 @@ ] } ], - "logGpsLocation": [ - "QUESTIONNAIRE" - ], + "logGpsLocation": [], "dateFormat": "MMM d, hh:mm aa" } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt index 1a9769680b1..340c258df8c 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt @@ -35,7 +35,6 @@ import androidx.core.os.bundleOf import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import com.google.android.fhir.datacapture.QuestionnaireFragment -import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.LocationServices import dagger.hilt.android.AndroidEntryPoint import java.io.Serializable @@ -78,7 +77,6 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { private lateinit var viewBinding: QuestionnaireActivityBinding private var questionnaire: Questionnaire? = null private var alertDialog: AlertDialog? = null - private lateinit var fusedLocationClient: FusedLocationProviderClient private var currentLocation: Location? = null private val locationPermissionLauncher: ActivityResultLauncher> = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { @@ -155,8 +153,6 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { if ( viewModel.applicationConfiguration.logGpsLocation.contains(LocationLogOptions.QUESTIONNAIRE) ) { - fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) - if (!LocationUtils.isLocationEnabled(this)) { showLocationSettingsDialog( Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply { @@ -195,6 +191,9 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { } fun fetchLocation(highAccuracy: Boolean = true) { + val fusedLocationClient = + LocationServices.getFusedLocationProviderClient(this@QuestionnaireActivity) + lifecycleScope.launch { try { currentLocation = @@ -252,11 +251,17 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { ) .build() viewBinding.clearAll.setOnClickListener { questionnaireFragment.clearAllAnswers() } - supportFragmentManager.commit { - setReorderingAllowed(true) - add(R.id.container, questionnaireFragment, QUESTIONNAIRE_FRAGMENT_TAG) - } - registerFragmentResultListener() + + supportFragmentManager + .takeIf { !it.isDestroyed } + ?.apply { + commit { + setReorderingAllowed(true) + add(R.id.container, questionnaireFragment, QUESTIONNAIRE_FRAGMENT_TAG) + } + + registerFragmentResultListener() + } viewModel.setProgressState(QuestionnaireProgressState.QuestionnaireLaunch(false)) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt index bf8c395ef62..9da025bf27f 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModel.kt @@ -87,7 +87,7 @@ import org.smartregister.fhircore.engine.util.extension.appendRelatedEntityLocat import org.smartregister.fhircore.engine.util.extension.asReference import org.smartregister.fhircore.engine.util.extension.batchedSearch import org.smartregister.fhircore.engine.util.extension.clearText -import org.smartregister.fhircore.engine.util.extension.cqfLibraryUrls +import org.smartregister.fhircore.engine.util.extension.cqfLibraryIds import org.smartregister.fhircore.engine.util.extension.extractByStructureMap import org.smartregister.fhircore.engine.util.extension.extractId import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid @@ -897,7 +897,7 @@ constructor( } val libraryFilters = - questionnaire.cqfLibraryUrls().map { + questionnaire.cqfLibraryIds().map { val apply: TokenParamFilterCriterion.() -> Unit = { value = of(it.extractLogicalIdUuid()) } apply } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactory.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactory.kt index 3bac623fb09..526a77f43f0 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactory.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactory.kt @@ -65,7 +65,11 @@ object EditTextQrCodeViewHolderFactory : } } !prevAnswerEmpty && newAnswerEmpty -> { - questionnaireViewItem.removeAnswer(previousAnswer!!) + if (canHaveMultipleAnswers) { + questionnaireViewItem.removeAnswer(previousAnswer!!) + } else { + questionnaireViewItem.clearAnswer() + } } !prevAnswerEmpty && !newAnswerEmpty -> { previousAnswer!!.value = newAnswer!!.value diff --git a/android/quest/src/test/assets/configs/app/application_config.json b/android/quest/src/test/assets/configs/app/application_config.json index bc04e9fc7da..6969721e843 100644 --- a/android/quest/src/test/assets/configs/app/application_config.json +++ b/android/quest/src/test/assets/configs/app/application_config.json @@ -77,7 +77,5 @@ } } ], - "logGpsLocation": [ - "QUESTIONNAIRE" - ] + "logGpsLocation": [] } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/FhirEngineProviderTestRule.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/FhirEngineProviderTestRule.kt index 8011dd2c7c0..7baad2fb5c4 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/FhirEngineProviderTestRule.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/FhirEngineProviderTestRule.kt @@ -19,28 +19,21 @@ package org.smartregister.fhircore.quest import com.google.android.fhir.FhirEngineConfiguration import com.google.android.fhir.FhirEngineProvider import org.junit.rules.TestRule +import org.junit.rules.TestWatcher import org.junit.runner.Description -import org.junit.runners.model.Statement /** A [TestRule] that cleans up [FhirEngineProvider] instance after each test run. */ -class FhirEngineProviderTestRule : TestRule { - override fun apply(base: Statement, p1: Description): Statement { - return object : Statement() { - override fun evaluate() { - try { - FhirEngineProvider.init(FhirEngineConfiguration(testMode = true)) - base.evaluate() - } catch (exception: IllegalStateException) { // Necessary to avoid crashing tests - println(exception) - } finally { - try { - FhirEngineProvider.cleanup() - } catch ( - e: IllegalStateException,) { // TODO investigate why testMode is false at this point - println(e) - } - } - } - } +class FhirEngineProviderTestRule : TestWatcher() { + + override fun starting(description: Description?) { + try { + FhirEngineProvider.init(FhirEngineConfiguration(testMode = true)) + } catch (_: IllegalStateException) {} + } + + override fun finished(description: Description?) { + try { + FhirEngineProvider.cleanup() + } catch (_: IllegalStateException) {} } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt index 34616b0c359..629cad80add 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/QuestApplicationTest.kt @@ -16,7 +16,6 @@ package org.smartregister.fhircore.quest -import android.content.Intent import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import io.mockk.every @@ -32,7 +31,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.smartregister.fhircore.quest.robolectric.RobolectricTest -import org.smartregister.fhircore.quest.ui.appsetting.AppSettingActivity @HiltAndroidTest class QuestApplicationTest : RobolectricTest() { @@ -93,15 +91,4 @@ class QuestApplicationTest : RobolectricTest() { Assert.assertNotNull(config) } - - @Test - fun testOnCreate() { - hiltRule.inject() - application.onCreate() - - val intent = Intent(application, AppSettingActivity::class.java) - application.startActivity(intent) - assertNotNull(application.referenceUrlResolver) - assertNotNull(application.xFhirQueryResolver) - } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/app/fakes/FakeKnowledgeManagerModule.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/app/fakes/FakeKnowledgeManagerModule.kt new file mode 100644 index 00000000000..dc896802fd2 --- /dev/null +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/app/fakes/FakeKnowledgeManagerModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2024 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.quest.app.fakes + +import android.content.Context +import com.google.android.fhir.knowledge.KnowledgeManager +import dagger.Module +import dagger.Provides +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import javax.inject.Singleton +import org.smartregister.fhircore.engine.di.KnowledgeManagerModule + +@Module +@TestInstallIn(components = [SingletonComponent::class], replaces = [KnowledgeManagerModule::class]) +class FakeKnowledgeManagerModule { + + @Provides + @Singleton + fun provideKnowledgeManager(@ApplicationContext context: Context): KnowledgeManager = + KnowledgeManager.create(context, inMemory = true) +} diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/data/report/measure/MeasureReportRepositoryTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/data/report/measure/MeasureReportRepositoryTest.kt index 8410d9d7f39..99a438a53e1 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/data/report/measure/MeasureReportRepositoryTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/data/report/measure/MeasureReportRepositoryTest.kt @@ -33,8 +33,8 @@ import io.mockk.runs import io.mockk.spyk import javax.inject.Inject import kotlin.test.assertEquals -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest import org.apache.commons.jexl3.JexlEngine import org.hl7.fhir.exceptions.FHIRException import org.hl7.fhir.r4.model.Group @@ -159,29 +159,27 @@ class MeasureReportRepositoryTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testEvaluatePopulationMeasureHandlesBadMeasureUrl() { - runBlocking(Dispatchers.Default) { - val measureReport = - measureReportRepository.evaluatePopulationMeasure( - measureUrl = "bad-measure-url", - startDateFormatted = today().firstDayOfMonth().formatDate(SDF_YYYY_MM_DD), - endDateFormatted = today().lastDayOfMonth().formatDate(SDF_YYYY_MM_DD), - subjects = emptyList(), - existing = emptyList(), - practitionerId = null, - ) - assertEquals(measureReport.size, 0) - } + fun testEvaluatePopulationMeasureHandlesBadMeasureUrl() = runTest { + val measureReport = + measureReportRepository.evaluatePopulationMeasure( + measureUrl = "bad-measure-url", + startDateFormatted = today().firstDayOfMonth().formatDate(SDF_YYYY_MM_DD), + endDateFormatted = today().lastDayOfMonth().formatDate(SDF_YYYY_MM_DD), + subjects = emptyList(), + existing = emptyList(), + practitionerId = null, + ) + assertEquals(measureReport.size, 0) } @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectsWithResultsEmptySubjectXFhir() { + fun testRetrieveSubjectsWithResultsEmptySubjectXFhir() = runTest { val reportConfiguration = ReportConfiguration() coEvery { fhirEngine.search(any()) } returns listOf(SearchResult(resource = Patient(), null, null)) - runBlocking(Dispatchers.Default) { + runBlocking(dispatcherProvider.default()) { val data = measureReportRepository.fetchSubjects(reportConfiguration) assertEquals(0, data.size) } @@ -191,12 +189,12 @@ class MeasureReportRepositoryTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectsWithResultsInvalidSubjectXFhir() { + fun testRetrieveSubjectsWithResultsInvalidSubjectXFhir() = runTest { val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "not-a-resource-type") coEvery { fhirEngine.search(any()) } returns listOf(SearchResult(resource = Patient(), null, null)) - runBlocking(Dispatchers.Default) { + runBlocking(dispatcherProvider.default()) { val data = measureReportRepository.fetchSubjects(reportConfiguration) assertEquals(0, data.size) } @@ -206,12 +204,12 @@ class MeasureReportRepositoryTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhir() { + fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhir() = runTest { val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "Patient") coEvery { fhirEngine.search(any()) } returns listOf(SearchResult(resource = Patient(), null, null)) - runBlocking(Dispatchers.Default) { + runBlocking(dispatcherProvider.default()) { val data = measureReportRepository.fetchSubjects(reportConfiguration) assertEquals(1, data.size) } @@ -221,14 +219,14 @@ class MeasureReportRepositoryTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhirWithGroupUpdates() { + fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhirWithGroupUpdates() = runTest { val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "Patient") val resource = Group().apply { id = "grp1" } coEvery { fhirEngine.search(any()) } returns listOf(SearchResult(resource = resource, null, null)) coEvery { fhirEngine.get(ResourceType.Group, resource.logicalId) } returns resource coEvery { fhirEngine.update(any()) } just runs - runBlocking(Dispatchers.Default) { + runBlocking(dispatcherProvider.default()) { val data = measureReportRepository.fetchSubjects(reportConfiguration) assertEquals(1, data.size) } @@ -238,38 +236,39 @@ class MeasureReportRepositoryTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhirWithNonEmptyGroupDoesNotUpdate() { - val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "Patient") - coEvery { fhirEngine.search(any()) } returns - listOf( - SearchResult( - resource = - Group() - .addMember( - Group.GroupMemberComponent().setEntity(Reference().setReference("Patient/1")), - ), - null, - null, - ), - ) - coEvery { fhirEngine.update(any()) } just runs + fun testRetrieveSubjectsWithResultsNonEmptySubjectXFhirWithNonEmptyGroupDoesNotUpdate() = + runTest { + val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "Patient") + coEvery { fhirEngine.search(any()) } returns + listOf( + SearchResult( + resource = + Group() + .addMember( + Group.GroupMemberComponent().setEntity(Reference().setReference("Patient/1")), + ), + null, + null, + ), + ) + coEvery { fhirEngine.update(any()) } just runs - runBlocking(Dispatchers.Default) { - val data = measureReportRepository.fetchSubjects(reportConfiguration) - assertEquals(1, data.size) - } + runBlocking(dispatcherProvider.default()) { + val data = measureReportRepository.fetchSubjects(reportConfiguration) + assertEquals(1, data.size) + } - coVerify { fhirEngine.search(any()) } - coVerify(inverse = true) { fhirEngine.update(any()) } - } + coVerify { fhirEngine.search(any()) } + coVerify(inverse = true) { fhirEngine.update(any()) } + } @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveSubjectHandlesFhirException() { + fun testRetrieveSubjectHandlesFhirException() = runTest { val reportConfiguration = ReportConfiguration(subjectXFhirQuery = "Patient") coEvery { fhirEngine.search(any()) } throws FHIRException("") - runBlocking(Dispatchers.Default) { + runBlocking(dispatcherProvider.default()) { val data = measureReportRepository.fetchSubjects(reportConfiguration) assertEquals(0, data.size) } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/robolectric/WorkManagerRule.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/robolectric/WorkManagerRule.kt index 515eb206881..bb0a9b7d905 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/robolectric/WorkManagerRule.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/robolectric/WorkManagerRule.kt @@ -17,34 +17,34 @@ package org.smartregister.fhircore.quest.robolectric import android.util.Log -import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import androidx.work.Configuration import androidx.work.WorkManager import androidx.work.testing.SynchronousExecutor import androidx.work.testing.WorkManagerTestInitHelper -import org.junit.rules.TestRule +import org.junit.rules.TestWatcher import org.junit.runner.Description -import org.junit.runners.model.Statement -class WorkManagerRule : TestRule { +class WorkManagerRule : TestWatcher() { - override fun apply(base: Statement, description: Description): Statement { - return object : Statement() { - override fun evaluate() { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val config = - Configuration.Builder() - .setMinimumLoggingLevel(Log.DEBUG) - .setExecutor(SynchronousExecutor()) - .build() - WorkManagerTestInitHelper.initializeTestWorkManager(context, config) - try { - base.evaluate() - } finally { - WorkManager.getInstance(ApplicationProvider.getApplicationContext()).cancelAllWork() - } - } - } + override fun starting(description: Description?) { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val config = + Configuration.Builder() + .setMinimumLoggingLevel(Log.DEBUG) + .setExecutor(SynchronousExecutor()) + .build() + WorkManagerTestInitHelper.initializeTestWorkManager(context, config) + } + + override fun finished(description: Description?) { + val context = InstrumentationRegistry.getInstrumentation().targetContext + try { + WorkManager.getInstance(context).cancelAllWork() + } catch (_: Exception) {} + + try { + WorkManagerTestInitHelper.closeWorkDatabase() + } catch (_: Exception) {} } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModelTest.kt index c04891a87dd..508a641ee04 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/appsetting/AppSettingViewModelTest.kt @@ -384,8 +384,8 @@ class AppSettingViewModelTest : RobolectricTest() { } @Test - fun `fetchConfigurations() with an ImplementationGuide should call fetchRemoteCompositionById()`() { - runBlocking { + fun `fetchConfigurations() with an ImplementationGuide should call fetchRemoteCompositionById()`() = + runTest { appSettingViewModel.run { onApplicationIdChanged("app") fetchConfigurations(context) @@ -412,20 +412,27 @@ class AppSettingViewModelTest : RobolectricTest() { ) } returns implementationGuide coEvery { appSettingViewModel.configurationRegistry.addOrUpdate(any()) } just runs + coEvery { + appSettingViewModel.configurationRegistry.loadConfigurations(any(), any(), any()) + } just runs coEvery { appSettingViewModel.configurationRegistry.fetchRemoteCompositionById(any(), any()) } returns composition coEvery { appSettingViewModel.defaultRepository.createRemote(any(), any()) } just runs + appSettingViewModel.fetchConfigurations(context) + coVerify { appSettingViewModel.configurationRegistry.fetchRemoteCompositionById(any(), any()) } + coVerify { + appSettingViewModel.configurationRegistry.loadConfigurations("app", context, any()) + } } - } @Test - fun `fetchConfigurations() without ImplementationGuide should call fetchRemoteCompositionByAppId()`() { - runBlocking { + fun `fetchConfigurations() without ImplementationGuide should call fetchRemoteCompositionByAppId()`() = + runTest { appSettingViewModel.run { onApplicationIdChanged("app") fetchConfigurations(context) @@ -440,9 +447,16 @@ class AppSettingViewModelTest : RobolectricTest() { coEvery { appSettingViewModel.configurationRegistry.fetchRemoteCompositionByAppId(any()) } returns composition + coEvery { + appSettingViewModel.configurationRegistry.loadConfigurations(any(), any(), any()) + } just runs coEvery { appSettingViewModel.defaultRepository.createRemote(any(), any()) } just runs + appSettingViewModel.fetchConfigurations(context) + coVerify { appSettingViewModel.configurationRegistry.fetchRemoteCompositionByAppId(any()) } + coVerify { + appSettingViewModel.configurationRegistry.loadConfigurations("app", context, any()) + } } - } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginActivityTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginActivityTest.kt index 3604b6c46fb..6e9cdafee42 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginActivityTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginActivityTest.kt @@ -19,7 +19,6 @@ package org.smartregister.fhircore.quest.ui.login import android.content.Context import android.content.Intent import androidx.compose.material.ExperimentalMaterialApi -import androidx.lifecycle.Observer import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import dagger.hilt.android.testing.BindValue @@ -84,19 +83,13 @@ class LoginActivityTest : RobolectricTest() { @Test fun testForgotPasswordLoadsContact() { - val launchDialPadObserver = - Observer { dialPadUri -> - if (dialPadUri != null) { - Assert.assertEquals("1234567890", dialPadUri) - } - } val context = InstrumentationRegistry.getInstrumentation().targetContext - try { - loginActivity.loginViewModel.launchDialPad.observeForever(launchDialPadObserver) - loginActivity.loginViewModel.forgotPassword(context) - } finally { - loginActivity.loginViewModel.launchDialPad.removeObserver(launchDialPadObserver) - } + loginActivity.loginViewModel.forgotPassword(context) + + val forgotPasswordContact = loginActivity.loginViewModel.launchDialPad.value + Assert.assertNotNull(forgotPasswordContact) + // Matches supervisor contact number as set in application's login config + Assert.assertEquals("1234567890", forgotPasswordContact) } @Test diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginViewModelTest.kt index 857b215b1be..0e97663a6a4 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/login/LoginViewModelTest.kt @@ -26,6 +26,7 @@ import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import io.mockk.CapturingSlot import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -211,11 +212,21 @@ internal class LoginViewModelTest : RobolectricTest() { } @Test - fun testSuccessfulOnlineLoginWithActiveSessionWithNoPractitionerDetailsSaved() { + fun testSuccessfulOnlineLoginWithActiveSessionWithNoPractitionerDetailsSaved() = runTest { updateCredentials() every { tokenAuthenticator.sessionActive() } returns true + coEvery { tokenAuthenticator.fetchAccessToken(any(), any()) } returns + Result.failure( + SocketTimeoutException(), + ) loginViewModel.login(mockedActivity(isDeviceOnline = true)) - Assert.assertFalse(loginViewModel.navigateToHome.value!!) + + coVerify { + tokenAuthenticator.fetchAccessToken( + thisUsername.trim(), + withArg { it.contentEquals(thisPassword.trim().toCharArray()) }, + ) + } } @Test diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt index 187cf9754c5..54c493c34d2 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModelTest.kt @@ -174,12 +174,14 @@ class AppMainViewModelTest : RobolectricTest() { } @Test - fun testOnEventUpdateSyncStates() { + fun testOnEventUpdateSyncStates() = runTest { // Simulate sync state Finished val syncFinishedTimestamp = OffsetDateTime.now() val syncFinishedSyncJobStatus = mockk() every { syncFinishedSyncJobStatus.timestamp } returns syncFinishedTimestamp + coEvery { registerRepository.countRegisterData(any()) } returns 0L + appMainViewModel.onEvent( AppMainEvent.UpdateSyncState( syncCounter = 1, diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/pin/PinViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/pin/PinViewModelTest.kt index a279eeafbca..a1497f18ecd 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/pin/PinViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/pin/PinViewModelTest.kt @@ -23,12 +23,12 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.platform.app.InstrumentationRegistry import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest -import io.mockk.Runs import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.mockkStatic +import io.mockk.runs import io.mockk.slot import io.mockk.unmockkStatic import io.mockk.verify @@ -128,8 +128,9 @@ class PinViewModelTest : RobolectricTest() { val newPinSlot = slot() val onSavedPinLambdaSlot = slot<() -> Unit>() - coEvery { secureSharedPreference.saveSessionPin(capture(newPinSlot), captureLambda()) } just - Runs + coEvery { + secureSharedPreference.saveSessionPin(capture(newPinSlot), capture(onSavedPinLambdaSlot)) + } just runs pinViewModel.onSetPin("1990".toCharArray()) diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt index aa99d157ae9..87650e7bc8e 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt @@ -20,7 +20,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.ui.platform.ComposeView import androidx.core.os.bundleOf import androidx.fragment.app.commitNow -import androidx.navigation.testing.TestNavHostController import androidx.test.core.app.ApplicationProvider import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule @@ -51,11 +50,10 @@ import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData import org.smartregister.fhircore.engine.domain.model.SnackBarMessageConfig import org.smartregister.fhircore.engine.util.DispatcherProvider -import org.smartregister.fhircore.quest.R import org.smartregister.fhircore.quest.app.fakes.Faker +import org.smartregister.fhircore.quest.app.fakes.HiltTestActivity import org.smartregister.fhircore.quest.navigation.NavigationArg import org.smartregister.fhircore.quest.robolectric.RobolectricTest -import org.smartregister.fhircore.quest.ui.main.AppMainActivity import org.smartregister.fhircore.quest.ui.profile.model.EligibleManagingEntity import org.smartregister.fhircore.quest.ui.shared.models.QuestionnaireSubmission @@ -74,16 +72,12 @@ class ProfileFragmentTest : RobolectricTest() { @BindValue lateinit var profileViewModel: ProfileViewModel - private val activityController = Robolectric.buildActivity(AppMainActivity::class.java) - - private lateinit var navController: TestNavHostController + private val activityController = Robolectric.buildActivity(HiltTestActivity::class.java) private val patient = Faker.buildPatient() private val resourceConfig = mockk() - private lateinit var mainActivity: AppMainActivity - lateinit var profileFragment: ProfileFragment @Before @@ -117,17 +111,16 @@ class ProfileFragmentTest : RobolectricTest() { ) } activityController.create().resume() - mainActivity = activityController.get() - navController = - TestNavHostController(mainActivity).apply { setGraph(R.navigation.application_nav_graph) } + activityController.get().apply { + supportFragmentManager.commitNow { + add(profileFragment, ProfileFragment::class.java.simpleName) + } + supportFragmentManager.executePendingTransactions() + } // Simulate the returned value of loadProfile coEvery { registerRepository.loadProfileData(any(), any(), paramsMap = emptyMap()) } returns RepositoryResourceData(resource = Faker.buildPatient()) - mainActivity.supportFragmentManager.run { - commitNow { add(profileFragment, ProfileFragment::class.java.simpleName) } - executePendingTransactions() - } } @Test @@ -155,7 +148,7 @@ class ProfileFragmentTest : RobolectricTest() { coVerify { profileViewModel.retrieveProfileUiState( - context = ApplicationProvider.getApplicationContext(), + context = profileFragment.requireContext(), profileId = "defaultProfile", resourceId = "sampleId", fhirResourceConfig = any(), @@ -191,7 +184,7 @@ class ProfileFragmentTest : RobolectricTest() { coVerify { profileViewModel.retrieveProfileUiState( - context = ApplicationProvider.getApplicationContext(), + context = profileFragment.requireContext(), profileId = "defaultProfile", resourceId = "sampleId", fhirResourceConfig = any(), diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModelTest.kt index 4e88825dd3e..8d2d8e8f332 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModelTest.kt @@ -31,8 +31,10 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkConstructor import io.mockk.runs import io.mockk.spyk +import io.mockk.unmockkConstructor import io.mockk.verifyAll import javax.inject.Inject import kotlin.test.assertEquals @@ -40,13 +42,17 @@ import kotlin.test.assertNotNull import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.hl7.fhir.r4.model.Group +import org.hl7.fhir.r4.model.HumanName import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.Reference import org.hl7.fhir.r4.model.ResourceType +import org.hl7.fhir.r4.model.StringType import org.junit.Before import org.junit.Rule import org.junit.Test import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.profile.ManagingEntityConfig +import org.smartregister.fhircore.engine.configuration.workflow.ActionTrigger import org.smartregister.fhircore.engine.configuration.workflow.ApplicationWorkflow import org.smartregister.fhircore.engine.data.local.ContentCache import org.smartregister.fhircore.engine.data.local.register.RegisterRepository @@ -58,7 +64,6 @@ import org.smartregister.fhircore.engine.rulesengine.ConfigRulesExecutor import org.smartregister.fhircore.engine.rulesengine.RulesExecutor import org.smartregister.fhircore.engine.util.DispatcherProvider import org.smartregister.fhircore.engine.util.extension.BLACK_COLOR_HEX_CODE -import org.smartregister.fhircore.engine.util.extension.getActivity import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor import org.smartregister.fhircore.quest.app.fakes.Faker import org.smartregister.fhircore.quest.robolectric.RobolectricTest @@ -143,15 +148,13 @@ class ProfileViewModelTest : RobolectricTest() { @Test @kotlinx.coroutines.ExperimentalCoroutinesApi - fun testRetrieveProfileUiState() { - runBlocking { - profileViewModel.retrieveProfileUiState( - context = ApplicationProvider.getApplicationContext(), - profileId = "householdProfile", - resourceId = "sampleId", - paramsList = emptyArray(), - ) - } + fun testRetrieveProfileUiState() = runTest { + profileViewModel.retrieveProfileUiState( + context = ApplicationProvider.getApplicationContext(), + profileId = "householdProfile", + resourceId = "sampleId", + paramsList = emptyArray(), + ) assertNotNull(profileViewModel.profileUiState.value) val theResourceData = profileViewModel.profileUiState.value.resourceData @@ -166,21 +169,23 @@ class ProfileViewModelTest : RobolectricTest() { } @Test - fun testProfileEventOnChangeManagingEntity() { + fun testProfileEventOnChangeManagingEntity() = runTest { + coEvery { registerRepository.changeManagingEntity(any(), any(), any()) } just runs + val managingEntityConfig = + ManagingEntityConfig( + eligibilityCriteriaFhirPathExpression = "Patient.active", + resourceType = ResourceType.Patient, + nameFhirPathExpression = "Patient.name.given", + ) profileViewModel.onEvent( ProfileEvent.OnChangeManagingEntity( ApplicationProvider.getApplicationContext(), eligibleManagingEntity = EligibleManagingEntity("groupId", "newId", memberInfo = "James Doe"), - managingEntityConfig = - ManagingEntityConfig( - eligibilityCriteriaFhirPathExpression = "Patient.active", - resourceType = ResourceType.Patient, - nameFhirPathExpression = "Patient.name.given", - ), + managingEntityConfig = managingEntityConfig, ), ) - coVerify { registerRepository.changeManagingEntity(any(), any(), any()) } + coVerify { registerRepository.changeManagingEntity("newId", "groupId", managingEntityConfig) } } @Test @@ -234,28 +239,38 @@ class ProfileViewModelTest : RobolectricTest() { @Test fun testThatManagingEntityProfileBottomSheetIsShownOnActionTriggered() = runTest { - val navController = mockk() - val event = mockk() val fragmentManager = mockk() val fragmentManagerTransaction = mockk() - val overflowMenuItemConfig = - OverflowMenuItemConfig( - id = 1, - title = "open profile bottom sheet", - confirmAction = false, - icon = null, - titleColor = BLACK_COLOR_HEX_CODE, - backgroundColor = null, - visible = "true", - showSeparator = false, - enabled = "true", - actions = emptyList(), - ) + val activity = + mockk { + every { supportFragmentManager } returns fragmentManager + every { supportFragmentManager.beginTransaction() } returns fragmentManagerTransaction + } + val navController = mockk { every { context } returns activity } + mockkConstructor(ProfileBottomSheetFragment::class) + every { + anyConstructed().show(any(), any()) + } just runs + + val memberPatient = + Patient().apply { + id = "entity1" + active = true + addName( + HumanName().apply { given = listOf(StringType("member 1")) }, + ) + } + val managingEntityResource = + mockk() { + every { id } returns "entity1" + every { entity } returns Reference().apply { reference = "Patient/entity1" } + } + val group = - Group().apply { managingEntity = managingEntity.apply { reference = "patient/1424251" } } - val managingEntityResource = mockk() - val profileBottomSheetFragment = mockk() - val activity = mockk() + Group().apply { + managingEntity = managingEntity.apply { reference = "patient/1424251" } + member = listOf(managingEntityResource) + } val viewModel = ProfileViewModel( @@ -269,7 +284,7 @@ class ProfileViewModelTest : RobolectricTest() { val managingEntityConfig = ManagingEntityConfig( nameFhirPathExpression = "name", - eligibilityCriteriaFhirPathExpression = "criteria", + eligibilityCriteriaFhirPathExpression = "active", resourceType = ResourceType.Patient, dialogTitle = "Change Managing Entity", dialogWarningMessage = "Warning", @@ -278,29 +293,50 @@ class ProfileViewModelTest : RobolectricTest() { managingEntityReassignedMessage = "Managing entity reassigned", ) + val actionConfig = + ActionConfig( + trigger = ActionTrigger.ON_CLICK, + workflow = ApplicationWorkflow.CHANGE_MANAGING_ENTITY.name, + managingEntity = managingEntityConfig, + ) + val overflowMenuItemConfig = + OverflowMenuItemConfig( + id = 1, + title = "open profile bottom sheet", + confirmAction = false, + icon = null, + titleColor = BLACK_COLOR_HEX_CODE, + backgroundColor = null, + visible = "true", + showSeparator = false, + enabled = "true", + actions = listOf(actionConfig), + ) + coEvery { registerRepository.loadResource("group1") } returns group - coEvery { group.member } returns listOf(managingEntityResource) - every { managingEntityResource.id } returns "entity1" - every { - fhirPathDataExtractor.extractValue( - managingEntityResource, - "name", + coEvery { registerRepository.loadResource("entity1", ResourceType.Patient) } returns + memberPatient + + val groupResourceData = + ResourceData( + baseResourceId = "group1", + baseResourceType = ResourceType.Group, + computedValuesMap = emptyMap(), ) - } returns "memebr 1" - every { activity.supportFragmentManager } returns fragmentManager - every { activity.supportFragmentManager.beginTransaction() } returns fragmentManagerTransaction + viewModel.onEvent( ProfileEvent.OverflowMenuClick( navController, - resourceData, + groupResourceData, overflowMenuItemConfig, ), ) - profileViewModel.changeManagingEntity(event, managingEntityConfig) verifyAll { navController.context - activity.getActivity() - profileBottomSheetFragment.show(fragmentManager, ProfileBottomSheetFragment.TAG) + anyConstructed() + .show(any(), ProfileBottomSheetFragment.TAG) } + + unmockkConstructor(ProfileBottomSheetFragment::class) } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivityTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivityTest.kt index 2c80d743971..a5fa387b896 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivityTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivityTest.kt @@ -24,9 +24,7 @@ import android.provider.Settings import android.widget.Toast import androidx.test.core.app.ApplicationProvider import com.google.android.fhir.FhirEngine -import com.google.android.fhir.datacapture.QuestionnaireFragment import com.google.android.fhir.db.ResourceNotFoundException -import com.google.android.gms.location.LocationServices import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest @@ -43,10 +41,7 @@ import javax.inject.Inject import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertTrue import kotlin.test.assertNotNull -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.hl7.fhir.r4.model.Enumerations import org.hl7.fhir.r4.model.Questionnaire @@ -61,9 +56,12 @@ import org.robolectric.Shadows.shadowOf import org.robolectric.android.controller.ActivityController import org.robolectric.shadows.ShadowAlertDialog import org.robolectric.shadows.ShadowToast +import org.smartregister.fhircore.engine.configuration.ConfigType import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.QuestionnaireConfig +import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration import org.smartregister.fhircore.engine.configuration.app.LocationLogOptions +import org.smartregister.fhircore.engine.data.local.ContentCache import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.domain.model.ActionParameter import org.smartregister.fhircore.engine.domain.model.ActionParameterType @@ -81,6 +79,8 @@ class QuestionnaireActivityTest : RobolectricTest() { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @Inject lateinit var fhirEngine: FhirEngine + + @Inject lateinit var contentCache: ContentCache private val context: Application = ApplicationProvider.getApplicationContext() private lateinit var questionnaireConfig: QuestionnaireConfig private lateinit var questionnaireJson: String @@ -102,6 +102,7 @@ class QuestionnaireActivityTest : RobolectricTest() { defaultRepository = mockk(relaxUnitFun = true) { every { fhirEngine } returns spyk(this@QuestionnaireActivityTest.fhirEngine) + every { contentCache } returns spyk(this@QuestionnaireActivityTest.contentCache) } questionnaireConfig = QuestionnaireConfig( @@ -166,76 +167,78 @@ class QuestionnaireActivityTest : RobolectricTest() { every { Toast.makeText(any(), any(), Toast.LENGTH_LONG) } returns mockk() { every { show() } just runs } setupActivity() - advanceUntilIdle() verify { Toast.makeText(any(), eq(context.getString(R.string.questionnaire_not_found)), any()) } unmockkStatic(Toast::class) } @Test - fun testThatActivityRendersConfiguredQuestionnaire() = - runTest(timeout = 90.seconds) { - // TODO verify that this test executes as expected - - // Questionnaire will be retrieved from the database - fhirEngine.create(questionnaire.apply { id = questionnaireConfig.id }) - - setupActivity() - Assert.assertTrue(questionnaireActivity.supportFragmentManager.fragments.isNotEmpty()) - val firstFragment = - questionnaireActivity.supportFragmentManager.fragments[ - questionnaireActivity.supportFragmentManager.fragments.size - 1, - ] - Assert.assertTrue(firstFragment is QuestionnaireFragment) - - // Questionnaire should be the same - val fragmentQuestionnaire = - firstFragment - ?.arguments - ?.getString("questionnaire") - ?.decodeResourceFromString() - - Assert.assertEquals(questionnaire.id, fragmentQuestionnaire?.id!!.extractLogicalIdUuid()) - val sortedQuestionnaireItemLinkIds = - questionnaire.item.map { it.linkId }.sorted().joinToString(",") - val sortedFragmentQuestionnaireItemLinkIds = - fragmentQuestionnaire?.item?.map { it.linkId }?.sorted()?.joinToString(",") - - Assert.assertEquals(sortedQuestionnaireItemLinkIds, sortedFragmentQuestionnaireItemLinkIds) - } + fun testThatActivityRendersConfiguredQuestionnaire() = runTest { + // TODO verify that this test executes as expected + + // Questionnaire will be retrieved from the database + fhirEngine.create(questionnaire.apply { id = questionnaireConfig.id }) - @Test - fun testThatOnBackPressShowsConfirmationAlertDialog() = runBlocking { setupActivity() - questionnaireActivity.onBackPressedDispatcher.onBackPressed() - val dialog = shadowOf(ShadowAlertDialog.getLatestAlertDialog()) - Assert.assertNotNull(dialog) + + val questionnaireFragment = + questionnaireActivity.supportFragmentManager.findFragmentByTag( + QuestionnaireActivity.QUESTIONNAIRE_FRAGMENT_TAG, + ) + Assert.assertNotNull(questionnaireFragment) + + // Questionnaire should be the same + val fragmentQuestionnaire = + questionnaireFragment + ?.arguments + ?.getString("questionnaire") + ?.decodeResourceFromString() + + Assert.assertEquals(questionnaire.id, fragmentQuestionnaire?.id!!.extractLogicalIdUuid()) + val sortedQuestionnaireItemLinkIds = + questionnaire.item.map { it.linkId }.sorted().joinToString(",") + val sortedFragmentQuestionnaireItemLinkIds = + fragmentQuestionnaire.item?.map { it.linkId }?.sorted()?.joinToString(",") + + Assert.assertEquals(sortedQuestionnaireItemLinkIds, sortedFragmentQuestionnaireItemLinkIds) } @Test - fun `setupLocationServices should open location settings if location is disabled`() { + fun `setupLocationServices should open location settings if location is disabled`() = runTest { + // Questionnaire will be retrieved from the database + fhirEngine.create(questionnaire.apply { id = questionnaireConfig.id }) + val initialConfig = + configurationRegistry.retrieveConfiguration(ConfigType.Application) + val logQuestionnaireGpsConfig = + initialConfig.copy(logGpsLocation = listOf(LocationLogOptions.QUESTIONNAIRE)) + configurationRegistry.configCacheMap[ConfigType.Application.name] = logQuestionnaireGpsConfig + + shadowOf(context).grantPermissions(android.Manifest.permission.ACCESS_FINE_LOCATION) + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, false) + locationManager.setTestProviderEnabled(LocationManager.NETWORK_PROVIDER, false) + setupActivity() assertTrue( questionnaireActivity.viewModel.applicationConfiguration.logGpsLocation.contains( LocationLogOptions.QUESTIONNAIRE, ), ) - - val fusedLocationProviderClient = - LocationServices.getFusedLocationProviderClient(questionnaireActivity) - assertNotNull(fusedLocationProviderClient) - - shadowOf(questionnaireActivity) - .grantPermissions(android.Manifest.permission.ACCESS_FINE_LOCATION) - val locationManager = - questionnaireActivity.getSystemService(Context.LOCATION_SERVICE) as LocationManager - locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, false) - locationManager.setTestProviderEnabled(LocationManager.NETWORK_PROVIDER, false) - - questionnaireActivity.fetchLocation() val startedIntent = shadowOf(questionnaireActivity).nextStartedActivity val expectedIntent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) assertEquals(expectedIntent.component, startedIntent.component) + // reset + configurationRegistry.configCacheMap[ConfigType.Application.name] = initialConfig + } + + @Test + fun testOnBackPressShowsConfirmationAlertDialog() = runTest { + // Questionnaire will be retrieved from the database + fhirEngine.create(questionnaire.apply { id = questionnaireConfig.id }) + setupActivity() + questionnaireActivity.onBackPressedDispatcher.onBackPressed() + val dialog = shadowOf(ShadowAlertDialog.getLatestAlertDialog()) + Assert.assertNotNull(dialog) } private fun setupActivity() { diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireDraftDialogViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireDraftDialogViewModelTest.kt index 4bcddcd8381..cf9d1490b14 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireDraftDialogViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireDraftDialogViewModelTest.kt @@ -29,8 +29,8 @@ import io.mockk.spyk import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import org.hl7.fhir.r4.model.AuditEvent import org.hl7.fhir.r4.model.QuestionnaireResponse import org.hl7.fhir.r4.model.ResourceType @@ -144,7 +144,7 @@ class QuestionnaireDraftDialogViewModelTest : RobolectricTest() { assertEquals("Questionnaire/dc-clinic-medicines", savedDraft.questionnaire) assertEquals("in-progress", savedDraft.status.toCode()) - runBlocking { + withContext(dispatcherProvider.io()) { questionnaireDraftDialogViewModel.deleteDraft(questionnaireConfig = questionnaireConfig) } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt index d94487be955..72441dff432 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireViewModelTest.kt @@ -51,11 +51,13 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import org.hl7.fhir.r4.model.Address import org.hl7.fhir.r4.model.Attachment import org.hl7.fhir.r4.model.Basic import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.CanonicalType import org.hl7.fhir.r4.model.CarePlan import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -858,7 +860,6 @@ class QuestionnaireViewModelTest : RobolectricTest() { } @Test - @Ignore("Re-check this test, it takes forever to run") fun testSaveDraftQuestionnaireCallsAddOrUpdateForPaginatedForms() = runTest { val pageItem = QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { @@ -1202,8 +1203,8 @@ class QuestionnaireViewModelTest : RobolectricTest() { samplePatientRegisterQuestionnaire.copy().apply { addExtension( Extension().apply { - url = "https://sample.cqf-library.url" - setValue(StringType("http://smartreg.org/Library/123")) + url = "http://hl7.org/fhir/StructureDefinition/cqf-library" + setValue(CanonicalType("Library/123")) }, ) } @@ -1225,13 +1226,15 @@ class QuestionnaireViewModelTest : RobolectricTest() { ) } - knowledgeManager.index( - File.createTempFile(cqlLibrary.name, ".json").apply { - this.writeText(cqlLibrary.encodeResourceToString()) - }, - ) + withContext(dispatcherProvider.io()) { + knowledgeManager.index( + File.createTempFile(cqlLibrary.name, ".json").apply { + this.writeText(cqlLibrary.encodeResourceToString()) + }, + ) + } - fhirEngine.create(patient) + fhirEngine.create(patient, cqlLibrary) questionnaireViewModel.executeCql(patient, bundle, questionnaire) @@ -1656,95 +1659,65 @@ class QuestionnaireViewModelTest : RobolectricTest() { } @Test - fun testSaveExtractedResourcesAddsRelatedEntityLocationMetaTagToExtractedResource() = - runBlocking { - val bundleSlot = slot() - val bundle = bundleSlot.captured - val linkId = "linkId" - val metaTagId = "UUId123" - val questionnaire = extractionQuestionnaire() - val questionnaireConfig = - questionnaireConfig.copy( - resourceIdentifier = "resourceId", - resourceType = ResourceType.Location, - saveQuestionnaireResponse = false, - type = "EDIT", - linkIds = listOf(LinkIdConfig(linkId = linkId, LinkIdType.LOCATION)), - extractedResourceUniquePropertyExpressions = - listOf( - ExtractedResourceUniquePropertyExpression( - ResourceType.Location, - "Observation.code.where(coding.code='obs1').coding.code", - ), + fun testSaveExtractedResourcesAddsRelatedEntityLocationMetaTagToExtractedResource() = runTest { + val bundle = Bundle() + val linkId = "linkId" + val metaTagId = "UUId123" + val questionnaire = extractionQuestionnaire() + val questionnaireConfig = + questionnaireConfig.copy( + resourceIdentifier = "resourceId", + resourceType = ResourceType.Location, + saveQuestionnaireResponse = false, + type = "EDIT", + linkIds = listOf(LinkIdConfig(linkId = linkId, LinkIdType.LOCATION)), + extractedResourceUniquePropertyExpressions = + listOf( + ExtractedResourceUniquePropertyExpression( + ResourceType.Location, + "Observation.code.where(coding.code='obs1').coding.code", ), - ) - val previousObs = - Observation().apply { - id = "previousObs1" - code = (CodeableConcept(Coding("http://obsys", "obs1", "Obs 1"))) - } - - val questionnaireResponse = - extractionQuestionnaireResponse().apply { - val extractionDate = Date() - subject = patient.asReference() - val listResource = - ListResource().apply { - id = metaTagId - status = ListResource.ListStatus.CURRENT - mode = ListResource.ListMode.WORKING - title = CONTAINED_LIST_TITLE - date = extractionDate - } - val listEntryComponent = - ListResource.ListEntryComponent().apply { - deleted = false - date = extractionDate - item = previousObs.asReference() - } - listResource.addEntry(listEntryComponent) - addContained(listResource) - } - - questionnaireViewModel.saveExtractedResources( - bundle = bundle, - questionnaire = questionnaire, - questionnaireConfig = questionnaireConfig, - questionnaireResponse = questionnaireResponse, - context = context, + ), ) + val previousObs = + Observation().apply { + id = "previousObs1" + code = (CodeableConcept(Coding("http://obsys", "obs1", "Obs 1"))) + } - // Extract tags manually - val listResource = questionnaireResponse.contained.firstOrNull() as ListResource - val resource = listResource.entry.firstOrNull()?.item?.resource + bundle.addEntry( + Bundle.BundleEntryComponent().apply { resource = previousObs }, + ) - // Assert - Assert.assertNotNull(listResource) - Assert.assertNotNull(resource) + val questionnaireResponse = extractionQuestionnaireResponse() - // Check if the meta tag id exists - var metaTagIdExists = false - resource?.meta?.tag?.forEach { tag -> - if (tag.code == metaTagId) { - metaTagIdExists = true - return@forEach - } - } - Assert.assertTrue(metaTagIdExists) - - // Check if the related entity location meta tag exists - var relatedEntityLocationMetaTagExists = false - resource?.meta?.tag?.forEach { tag -> - if (tag.system == "https://smartregister.org/related-entity-location-tag-id") { - relatedEntityLocationMetaTagExists = true - return@forEach - } - } - Assert.assertTrue(relatedEntityLocationMetaTagExists) + questionnaireViewModel.saveExtractedResources( + bundle = bundle, + questionnaire = questionnaire, + questionnaireConfig = questionnaireConfig, + questionnaireResponse = questionnaireResponse, + context = context, + ) - // Assert that the listResource id matches the linkId - assertEquals(linkId, listResource.id) + // Extract tags manually + val listResource = questionnaireResponse.contained.firstOrNull() as ListResource + val resourceRef = listResource.entry.firstOrNull()?.item + + // Assert + Assert.assertNotNull(listResource) + Assert.assertNotNull(resourceRef) + Assert.assertEquals("Observation/previousObs1", resourceRef?.reference) + + // Check if the related entity location meta tag exists + var relatedEntityLocationMetaTagExists = false + previousObs.meta?.tag?.forEach { tag -> + if (tag.system == "https://smartregister.org/related-entity-location-tag-id") { + relatedEntityLocationMetaTagExists = true + return@forEach + } } + Assert.assertTrue(relatedEntityLocationMetaTagExists) + } @Test fun testRetireUsedQuestionnaireUniqueIdShouldUpdateGroupResourceWhenIDIsUsed() = runTest { @@ -2302,7 +2275,6 @@ class QuestionnaireViewModelTest : RobolectricTest() { coEvery { defaultRepository.addOrUpdate(any(Boolean::class), any()) } just runs val medRequestSlot = slot() - val stringSlot = slot() val linkIdConfig = LinkIdConfig( resourceType = ResourceType.MedicationRequest, @@ -2322,7 +2294,7 @@ class QuestionnaireViewModelTest : RobolectricTest() { ) coVerify { defaultRepository.addOrUpdate(true, capture(medRequestSlot)) } - assertEquals("med-id-2", stringSlot.captured) + assertEquals("med-id-2", medRequestSlot.captured.logicalId) assertEquals("STOPPED", medRequestSlot.captured.status.name) } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt index 5a4c7b22b25..e37b1ec16d3 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/register/RegisterFragmentTest.kt @@ -20,7 +20,6 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.SnackbarDuration import androidx.core.os.bundleOf import androidx.fragment.app.commitNow -import androidx.navigation.testing.TestNavHostController import com.google.android.fhir.sync.CurrentSyncJobStatus import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidRule @@ -54,10 +53,11 @@ import org.smartregister.fhircore.engine.domain.model.ToolBarHomeNavigation import org.smartregister.fhircore.engine.sync.SyncState import org.smartregister.fhircore.engine.util.DispatcherProvider import org.smartregister.fhircore.quest.app.fakes.Faker +import org.smartregister.fhircore.quest.app.fakes.HiltTestActivity import org.smartregister.fhircore.quest.event.EventBus import org.smartregister.fhircore.quest.navigation.NavigationArg import org.smartregister.fhircore.quest.robolectric.RobolectricTest -import org.smartregister.fhircore.quest.ui.main.AppMainActivity +import org.smartregister.fhircore.quest.ui.main.AppMainViewModel import org.smartregister.fhircore.quest.ui.shared.models.QuestionnaireSubmission import org.smartregister.fhircore.quest.util.extensions.interpolateActionParamsValue @@ -75,11 +75,10 @@ class RegisterFragmentTest : RobolectricTest() { @BindValue lateinit var registerViewModel: RegisterViewModel - private lateinit var navController: TestNavHostController + @BindValue lateinit var appMainViewModel: AppMainViewModel + private lateinit var registerFragment: RegisterFragment - private lateinit var mainActivity: AppMainActivity - private lateinit var registerFragmentMock: RegisterFragment - private val activityController = Robolectric.buildActivity(AppMainActivity::class.java) + private val activityController = Robolectric.buildActivity(HiltTestActivity::class.java) @Before fun setUp() { @@ -90,11 +89,11 @@ class RegisterFragmentTest : RobolectricTest() { registerRepository = mockk(relaxed = true), configurationRegistry = configurationRegistry, sharedPreferencesHelper = Faker.buildSharedPreferencesHelper(), - rulesExecutor = mockk(), + rulesExecutor = mockk(relaxed = true), dispatcherProvider = dispatcherProvider, ), ) - registerFragmentMock = mockk() + appMainViewModel = mockk(relaxed = true) registerFragment = RegisterFragment().apply { arguments = @@ -117,12 +116,7 @@ class RegisterFragmentTest : RobolectricTest() { } activityController.create().resume() - mainActivity = activityController.get() - navController = - TestNavHostController(mainActivity).apply { - setGraph(org.smartregister.fhircore.quest.R.navigation.application_nav_graph) - } - mainActivity.supportFragmentManager.run { + activityController.get().supportFragmentManager.run { commitNow { add(registerFragment, RegisterFragment::class.java.simpleName) } executePendingTransactions() } @@ -131,6 +125,7 @@ class RegisterFragmentTest : RobolectricTest() { @Test fun testOnSyncState() { val syncJobStatus = CurrentSyncJobStatus.Succeeded(OffsetDateTime.now()) + val registerFragmentMock = mockk() coEvery { registerFragmentMock.onSync(SyncState(currentSyncJobStatus = syncJobStatus, counter = 1)) } just runs @@ -197,7 +192,7 @@ class RegisterFragmentTest : RobolectricTest() { } @Test - fun testOnSyncWithFailedJobStatusNonAuthErrorRendersSyncFailedMessage() { + fun testOnSyncWithFailedJobStatusNonAuthErrorUpdatesAppDrawerUIState() { val syncJobStatus = CurrentSyncJobStatus.Failed(OffsetDateTime.now()) val registerFragmentSpy = spyk(registerFragment) registerFragmentSpy.onSync(SyncState(currentSyncJobStatus = syncJobStatus, counter = 1)) @@ -205,14 +200,12 @@ class RegisterFragmentTest : RobolectricTest() { registerFragmentSpy.onSync(SyncState(currentSyncJobStatus = syncJobStatus, counter = 1)) } verify { - registerFragmentSpy.getString( - org.smartregister.fhircore.engine.R.string.sync_completed_with_errors, - ) + appMainViewModel.updateAppDrawerUIState(syncCounter = 1, currentSyncJobStatus = syncJobStatus) } } @Test - fun testOnSyncWithFailedJobStatusNonAuthErrorNullExceptionsRendersSyncFailedMessage() { + fun testOnSyncWithFailedJobStatusNonAuthErrorNullExceptionsUpdatesAppDrawerUIState() { val syncJobStatus: CurrentSyncJobStatus.Failed = mockk() val registerFragmentSpy = spyk(registerFragment) @@ -221,9 +214,7 @@ class RegisterFragmentTest : RobolectricTest() { registerFragmentSpy.onSync(SyncState(currentSyncJobStatus = syncJobStatus, counter = 1)) } verify { - registerFragmentSpy.getString( - org.smartregister.fhircore.engine.R.string.sync_completed_with_errors, - ) + appMainViewModel.updateAppDrawerUIState(syncCounter = 1, currentSyncJobStatus = syncJobStatus) } } } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactoryTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactoryTest.kt index 3803f62fb2a..afe91c8efae 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactoryTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/sdc/qrCode/EditTextQrCodeViewHolderFactoryTest.kt @@ -632,7 +632,45 @@ class EditTextQrCodeViewHolderFactoryTest : RobolectricTest() { } @Test - fun `onQrCodeChanged removes answer when new answer is empty`() = runTest { + fun `onQrCodeChanged clears answer when new answer is empty and item does not repeat`() = + runTest { + val viewHolder = EditTextQrCodeViewHolderFactory.create(parentView) + val previousQrAnswer = + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + value = StringType(sampleQrCode1) + } + val questionnaireViewItem = + spyk( + QuestionnaireViewItem( + questionnaireItem = + Questionnaire.QuestionnaireItemComponent().apply { + linkId = "linkId-a" + repeats = false + addExtension( + Extension( + "https://github.com/opensrp/android-fhir/StructureDefinition/qr-code-widget", + ), + ) + }, + questionnaireResponseItem = + QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { + linkId = "linkId-a" + addAnswer(previousQrAnswer) + }, + validationResult = NotValidated, + answersChangedCallback = { _, _, _, _ -> }, + ), + ) + viewHolder.bind(questionnaireViewItem) + val qrCodeAdapter = + viewHolder.itemView.findViewById(R.id.recycler_view_qr_codes).adapter + as QrCodeViewItemAdapter + qrCodeAdapter.qrCodeAnswerChangeListener.onQrCodeChanged(previousQrAnswer, null) + coVerify { questionnaireViewItem.clearAnswer() } + } + + @Test + fun `onQrCodeChanged removes answer when new answer is empty and item repeats`() = runTest { val viewHolder = EditTextQrCodeViewHolderFactory.create(parentView) val previousQrAnswer = QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { @@ -644,7 +682,7 @@ class EditTextQrCodeViewHolderFactoryTest : RobolectricTest() { questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { linkId = "linkId-a" - repeats = false + repeats = true addExtension( Extension( "https://github.com/opensrp/android-fhir/StructureDefinition/qr-code-widget",