From dbbfb5530786fe95d1b578cecdc67ec398398a12 Mon Sep 17 00:00:00 2001 From: jingtang10 Date: Tue, 9 Mar 2021 23:23:54 +0000 Subject: [PATCH] Use ktfmt and use google's kotlin style. --- build.gradle | 3 +- .../android/fhir/db/impl/DatabaseImplTest.kt | 475 ++++--- .../com/google/android/fhir/FhirEngine.kt | 94 +- .../google/android/fhir/FhirEngineBuilder.kt | 43 +- .../com/google/android/fhir/FhirServices.kt | 81 +- .../fhir/ResourceAlreadyExistsException.kt | 7 +- .../android/fhir/ResourceNotFoundException.kt | 7 +- .../main/java/com/google/android/fhir/Util.kt | 13 +- .../fhir/cql/AndroidR4FhirModelResolver.kt | 61 +- .../fhir/cql/FhirEngineDataProvider.kt | 21 +- .../fhir/cql/FhirEngineLibraryLoader.kt | 73 +- .../fhir/cql/FhirEngineRetrieveProvider.kt | 88 +- .../fhir/cql/FhirEngineTerminologyProvider.kt | 8 +- .../com/google/android/fhir/db/Database.kt | 280 ++-- .../db/ResourceAlreadyExistsInDbException.kt | 10 +- .../fhir/db/ResourceNotFoundInDbException.kt | 6 +- .../android/fhir/db/impl/DatabaseImpl.kt | 311 +++-- .../android/fhir/db/impl/DbTypeConverters.kt | 83 +- .../android/fhir/db/impl/ResourceDatabase.kt | 38 +- .../fhir/db/impl/dao/LocalChangeDao.kt | 178 +-- .../fhir/db/impl/dao/LocalChangeUtils.kt | 163 ++- .../android/fhir/db/impl/dao/ResourceDao.kt | 345 ++--- .../fhir/db/impl/dao/SyncedResourceDao.kt | 20 +- .../fhir/db/impl/entities/DateIndexEntity.kt | 47 +- .../db/impl/entities/LocalChangeEntity.kt | 54 +- .../db/impl/entities/NumberIndexEntity.kt | 46 +- .../db/impl/entities/QuantityIndexEntity.kt | 46 +- .../db/impl/entities/ReferenceIndexEntity.kt | 46 +- .../fhir/db/impl/entities/ResourceEntity.kt | 18 +- .../db/impl/entities/StringIndexEntity.kt | 46 +- .../db/impl/entities/SyncedResourceEntity.kt | 13 +- .../fhir/db/impl/entities/TokenIndexEntity.kt | 46 +- .../fhir/db/impl/entities/UriIndexEntity.kt | 46 +- .../android/fhir/impl/FhirEngineImpl.kt | 215 ++- .../android/fhir/index/ResourceIndexer.kt | 601 ++++----- .../android/fhir/index/ResourceIndices.kt | 73 +- .../android/fhir/index/entities/DateIndex.kt | 27 +- .../fhir/index/entities/NumberIndex.kt | 12 +- .../fhir/index/entities/QuantityIndex.kt | 10 +- .../fhir/index/entities/ReferenceIndex.kt | 12 +- .../fhir/index/entities/StringIndex.kt | 12 +- .../android/fhir/index/entities/TokenIndex.kt | 16 +- .../android/fhir/index/entities/UriIndex.kt | 6 +- .../google/android/fhir/resource/Resources.kt | 36 +- .../com/google/android/fhir/search/Search.kt | 38 +- .../fhir/search/filter/AndFilterCriterion.kt | 25 +- .../fhir/search/filter/FilterCriterion.kt | 10 +- .../fhir/search/filter/OrFilterCriterion.kt | 21 +- .../search/filter/ReferenceFilterCriterion.kt | 18 +- .../search/filter/StringFilterCriterion.kt | 22 +- .../google/android/fhir/search/impl/Query.kt | 8 +- .../fhir/search/impl/ResourceIdQuery.kt | 4 +- .../android/fhir/search/impl/SearchImpl.kt | 54 +- .../search/impl/SerializedResourceQuery.kt | 88 +- .../android/fhir/search/sort/SortCriterion.kt | 6 +- .../fhir/search/sort/StringSortCriterion.kt | 13 +- .../android/fhir/sync/FhirDataSource.kt | 10 +- .../android/fhir/sync/FhirSynchronizer.kt | 50 +- .../fhir/sync/PeriodicSyncConfiguration.kt | 40 +- .../android/fhir/sync/PeriodicSyncWorker.kt | 26 +- .../android/fhir/sync/ResourceSynchronizer.kt | 112 +- .../android/fhir/sync/SyncConfiguration.kt | 20 +- .../com/google/android/fhir/sync/SyncData.kt | 28 +- .../google/android/fhir/sync/SyncWorkType.kt | 8 +- .../android/fhir/resource/TestingUtils.kt | 68 +- .../android/fhir/impl/FhirEngineImplTest.kt | 177 +-- .../fhir/index/impl/ResourceIndexerTest.kt | 723 +++++----- .../android/fhir/resource/ResourcesTest.kt | 16 +- .../impl/SerializedResourceQueryTest.kt | 597 +++++---- ...eckBoxViewHolderFactoryInstrumentedTest.kt | 172 +-- ...PickerViewHolderFactoryInstrumentedTest.kt | 149 ++- ...PickerViewHolderFactoryInstrumentedTest.kt | 166 +-- ...isplayViewHolderFactoryInstrumentedTest.kt | 107 +- ...opDownViewHolderFactoryInstrumentedTest.kt | 356 +++-- ...ecimalViewHolderFactoryInstrumentedTest.kt | 203 +-- ...ntegerViewHolderFactoryInstrumentedTest.kt | 203 +-- ...tiLineViewHolderFactoryInstrumentedTest.kt | 238 ++-- ...antityViewHolderFactoryInstrumentedTest.kt | 267 ++-- ...leLineViewHolderFactoryInstrumentedTest.kt | 238 ++-- ...mGroupViewHolderFactoryInstrumentedTest.kt | 112 +- ...oGroupViewHolderFactoryInstrumentedTest.kt | 475 ++++--- .../fhir/datacapture/MoreAnswerOptions.kt | 36 +- .../MoreQuestionnaireItemExtensions.kt | 31 +- .../datacapture/MoreQuestionnaireItemTypes.kt | 19 +- .../fhir/datacapture/QuestionnaireFragment.kt | 63 +- .../datacapture/QuestionnaireItemAdapter.kt | 174 ++- .../QuestionnaireItemViewHolderType.kt | 32 +- .../datacapture/QuestionnaireViewModel.kt | 211 ++- .../enablement/EnablementEvaluator.kt | 151 +-- .../datacapture/mapping/ResourceMapper.kt | 130 +- .../views/DatePickerDialogFragment.kt | 63 +- ...stionnaireItemCheckBoxViewHolderFactory.kt | 51 +- ...ionnaireItemDatePickerViewHolderFactory.kt | 183 ++- ...aireItemDateTimePickerViewHolderFactory.kt | 261 ++-- ...estionnaireItemDisplayViewHolderFactory.kt | 36 +- ...stionnaireItemDropDownViewHolderFactory.kt | 90 +- ...ireItemEditTextDecimalViewHolderFactory.kt | 41 +- ...ireItemEditTextIntegerViewHolderFactory.kt | 41 +- ...eItemEditTextMultiLineViewHolderFactory.kt | 6 +- ...reItemEditTextQuantityViewHolderFactory.kt | 51 +- ...ItemEditTextSingleLineViewHolderFactory.kt | 6 +- ...ireItemEditTextStringViewHolderDelegate.kt | 39 +- ...stionnaireItemEditTextViewHolderFactory.kt | 60 +- ...QuestionnaireItemGroupViewHolderFactory.kt | 36 +- ...ionnaireItemRadioGroupViewHolderFactory.kt | 86 +- .../QuestionnaireItemViewHolderFactory.kt | 59 +- .../views/QuestionnaireItemViewItem.kt | 35 +- .../views/TimePickerDialogFragment.kt | 41 +- .../fhir/datacapture/MoreAnswerOptionsTest.kt | 157 +-- .../MoreQuestionnaireItemExtensionsTest.kt | 264 ++-- .../QuestionnaireItemAdapterTest.kt | 783 ++++++----- .../QuestionnaireItemViewHolderTypeTest.kt | 18 +- .../datacapture/QuestionnaireViewModelTest.kt | 289 ++-- .../enablement/EnablementEvaluatorTest.kt | 1166 +++++++++-------- .../datacapture/mapping/ResourceMapperTest.kt | 51 +- .../views/QuestionnaireItemViewItemTest.kt | 96 +- .../gallery/ExampleInstrumentedTest.kt | 12 +- .../fhir/datacapture/gallery/MainActivity.kt | 102 +- .../gallery/QuestionnaireActivity.kt | 93 +- .../gallery/QuestionnaireListAdapter.kt | 64 +- .../QuestionnaireResponseDialogFragment.kt | 31 +- .../gallery/QuestionnaireViewModel.kt | 26 +- .../fhir/reference/ExampleInstrumentedTest.kt | 12 +- .../fhir/reference/CqlActivityViewModel.kt | 56 +- .../android/fhir/reference/CqlLoadActivity.kt | 187 ++- .../android/fhir/reference/FhirApplication.kt | 53 +- .../ObservationItemRecyclerViewAdapter.kt | 48 +- .../reference/ObservationItemViewHolder.kt | 18 +- .../fhir/reference/PatientDetailActivity.kt | 64 +- .../fhir/reference/PatientDetailFragment.kt | 99 +- .../PatientItemRecyclerViewAdapter.kt | 49 +- .../fhir/reference/PatientItemViewHolder.kt | 36 +- .../fhir/reference/PatientListActivity.kt | 162 +-- .../fhir/reference/PatientListViewModel.kt | 181 +-- .../fhir/reference/api/FhirConverter.kt | 24 +- .../fhir/reference/api/HapiFhirService.kt | 37 +- .../reference/data/FhirPeriodicSyncWorker.kt | 12 +- .../data/HapiFhirResourceDataSource.kt | 14 +- .../fhir/reference/data/SamplePatients.kt | 191 ++- 139 files changed, 7531 insertions(+), 7600 deletions(-) diff --git a/build.gradle b/build.gradle index 9bc2ef54bc..395c7c4da2 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,8 @@ subprojects { spotless { kotlin { target '**/*.kt' - ktlint()userData(['max_line_length': '100']) + ktlint().userData(['indent_size': '2', 'continuation_indent_size': '2']) + ktfmt().googleStyle() licenseHeaderFile "${project.rootProject.projectDir}/license-header.txt" } } diff --git a/core/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt b/core/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt index cca50f7027..2671118ee7 100644 --- a/core/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt +++ b/core/src/androidTest/java/com/google/android/fhir/db/impl/DatabaseImplTest.kt @@ -40,273 +40,270 @@ import org.junit.runner.RunWith * recommend because: * * Different versions of android are shipped with different versions of SQLite. Integration tests * allow for better coverage on them. - * * Robolectric's SQLite implementation does not match Android, e.g.: https://github.com/robolectric/robolectric/blob/master/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteConnection.java#L97 + * * Robolectric's SQLite implementation does not match Android, e.g.: + * https://github.com/robolectric/robolectric/blob/master/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteConnection.java#L97 */ @RunWith(AndroidJUnit4::class) class DatabaseImplTest { - private val dataSource = object : FhirDataSource { - override suspend fun loadData(path: String): Bundle { - return Bundle() - } + private val dataSource = + object : FhirDataSource { + override suspend fun loadData(path: String): Bundle { + return Bundle() + } } - private val services = - FhirServices.builder(dataSource, ApplicationProvider.getApplicationContext()) - .inMemory() - .build() - private val testingUtils = TestingUtils(services.parser) - private val database = services.database + private val services = + FhirServices.builder(dataSource, ApplicationProvider.getApplicationContext()).inMemory().build() + private val testingUtils = TestingUtils(services.parser) + private val database = services.database - @Before - fun setUp() { - database.insert(TEST_PATIENT_1) - } + @Before + fun setUp() { + database.insert(TEST_PATIENT_1) + } - @Test - fun insert_shouldInsertResource() { - database.insert(TEST_PATIENT_2) - testingUtils.assertResourceEquals( - TEST_PATIENT_2, - database.select(Patient::class.java, TEST_PATIENT_2_ID) - ) - } + @Test + fun insert_shouldInsertResource() { + database.insert(TEST_PATIENT_2) + testingUtils.assertResourceEquals( + TEST_PATIENT_2, + database.select(Patient::class.java, TEST_PATIENT_2_ID) + ) + } - @Test - fun insertAll_shouldInsertResources() { - val patients = ArrayList() - patients.add(TEST_PATIENT_1) - patients.add(TEST_PATIENT_2) - database.insertAll(patients) - testingUtils.assertResourceEquals( - TEST_PATIENT_1, - database.select(Patient::class.java, TEST_PATIENT_1_ID) - ) - testingUtils.assertResourceEquals( - TEST_PATIENT_2, - database.select(Patient::class.java, TEST_PATIENT_2_ID) - ) - } + @Test + fun insertAll_shouldInsertResources() { + val patients = ArrayList() + patients.add(TEST_PATIENT_1) + patients.add(TEST_PATIENT_2) + database.insertAll(patients) + testingUtils.assertResourceEquals( + TEST_PATIENT_1, + database.select(Patient::class.java, TEST_PATIENT_1_ID) + ) + testingUtils.assertResourceEquals( + TEST_PATIENT_2, + database.select(Patient::class.java, TEST_PATIENT_2_ID) + ) + } - @Test - fun update_existentResource_shouldUpdateResource() { - val patient = Patient() - patient.setId(TEST_PATIENT_1_ID) - patient.setGender(Enumerations.AdministrativeGender.FEMALE) - database.update(patient) - testingUtils.assertResourceEquals( - patient, - database.select(Patient::class.java, TEST_PATIENT_1_ID) - ) - } + @Test + fun update_existentResource_shouldUpdateResource() { + val patient = Patient() + patient.setId(TEST_PATIENT_1_ID) + patient.setGender(Enumerations.AdministrativeGender.FEMALE) + database.update(patient) + testingUtils.assertResourceEquals( + patient, + database.select(Patient::class.java, TEST_PATIENT_1_ID) + ) + } - @Test - fun update_nonExistingResource_shouldNotInsertResource() { - val resourceNotFoundInDbException = - assertThrows(ResourceNotFoundInDbException::class.java) { - database.update(TEST_PATIENT_2) - } - /* ktlint-disable max-line-length */ - assertThat(resourceNotFoundInDbException.message) - .isEqualTo("Resource not found with type ${TEST_PATIENT_2.resourceType.name} and id $TEST_PATIENT_2_ID!" + @Test + fun update_nonExistingResource_shouldNotInsertResource() { + val resourceNotFoundInDbException = + assertThrows(ResourceNotFoundInDbException::class.java) { database.update(TEST_PATIENT_2) } + /* ktlint-disable max-line-length */ + assertThat(resourceNotFoundInDbException.message) + .isEqualTo( + "Resource not found with type ${TEST_PATIENT_2.resourceType.name} and id $TEST_PATIENT_2_ID!" /* ktlint-enable max-line-length */ ) - } + } - @Test - fun select_invalidResourceType_shouldThrowIllegalArgumentException() { - val illegalArgumentException = - assertThrows(IllegalArgumentException::class.java) { - database.select(Resource::class.java, "resource_id") - } - assertThat(illegalArgumentException.message) - .isEqualTo("Cannot resolve resource type for " + Resource::class.java.name) - } + @Test + fun select_invalidResourceType_shouldThrowIllegalArgumentException() { + val illegalArgumentException = + assertThrows(IllegalArgumentException::class.java) { + database.select(Resource::class.java, "resource_id") + } + assertThat(illegalArgumentException.message) + .isEqualTo("Cannot resolve resource type for " + Resource::class.java.name) + } - @Test - fun select_nonexistentResource_shouldThrowResourceNotFoundException() { - val resourceNotFoundException = - assertThrows(ResourceNotFoundInDbException::class.java) { - database.select(Patient::class.java, "nonexistent_patient") - } - assertThat(resourceNotFoundException.message) - .isEqualTo("Resource not found with type Patient and id nonexistent_patient!") - } + @Test + fun select_nonexistentResource_shouldThrowResourceNotFoundException() { + val resourceNotFoundException = + assertThrows(ResourceNotFoundInDbException::class.java) { + database.select(Patient::class.java, "nonexistent_patient") + } + assertThat(resourceNotFoundException.message) + .isEqualTo("Resource not found with type Patient and id nonexistent_patient!") + } - @Test - fun select_shouldReturnResource() { - testingUtils.assertResourceEquals( - TEST_PATIENT_1, - database.select(Patient::class.java, TEST_PATIENT_1_ID) - ) - } + @Test + fun select_shouldReturnResource() { + testingUtils.assertResourceEquals( + TEST_PATIENT_1, + database.select(Patient::class.java, TEST_PATIENT_1_ID) + ) + } - @Test - fun insert_shouldAddInsertLocalChange() { - val testPatient2String = services.parser.encodeResourceToString(TEST_PATIENT_2) - database.insert(TEST_PATIENT_2) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(TEST_PATIENT_2_ID) }.second - assertThat(type).isEqualTo(LocalChangeEntity.Type.INSERT) - assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) - assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) - assertThat(payload).isEqualTo(testPatient2String) - } + @Test + fun insert_shouldAddInsertLocalChange() { + val testPatient2String = services.parser.encodeResourceToString(TEST_PATIENT_2) + database.insert(TEST_PATIENT_2) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().single { it.second.resourceId.equals(TEST_PATIENT_2_ID) }.second + assertThat(type).isEqualTo(LocalChangeEntity.Type.INSERT) + assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) + assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) + assertThat(payload).isEqualTo(testPatient2String) + } - @Test - fun update_insertAndUpdate_shouldAddUpdateLocalChange() { - var patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insert(patient) - patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") - database.update(patient) - val patientString = services.parser.encodeResourceToString(patient) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(patient.id) }.second - assertThat(type).isEqualTo(LocalChangeEntity.Type.INSERT) - assertThat(resourceId).isEqualTo(patient.id) - assertThat(resourceType).isEqualTo(patient.resourceType.name) - assertThat(payload).isEqualTo(patientString) - } + @Test + fun update_insertAndUpdate_shouldAddUpdateLocalChange() { + var patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insert(patient) + patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") + database.update(patient) + val patientString = services.parser.encodeResourceToString(patient) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().single { it.second.resourceId.equals(patient.id) }.second + assertThat(type).isEqualTo(LocalChangeEntity.Type.INSERT) + assertThat(resourceId).isEqualTo(patient.id) + assertThat(resourceType).isEqualTo(patient.resourceType.name) + assertThat(payload).isEqualTo(patientString) + } - @Test - fun delete_shouldAddDeleteLocalChange() { - database.delete(Patient::class.java, TEST_PATIENT_1_ID) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(TEST_PATIENT_1_ID) }.second - assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) - assertThat(resourceId).isEqualTo(TEST_PATIENT_1_ID) - assertThat(resourceType).isEqualTo(TEST_PATIENT_1.resourceType.name) - assertThat(payload).isEmpty() - } + @Test + fun delete_shouldAddDeleteLocalChange() { + database.delete(Patient::class.java, TEST_PATIENT_1_ID) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().single { it.second.resourceId.equals(TEST_PATIENT_1_ID) }.second + assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) + assertThat(resourceId).isEqualTo(TEST_PATIENT_1_ID) + assertThat(resourceType).isEqualTo(TEST_PATIENT_1.resourceType.name) + assertThat(payload).isEmpty() + } - @Test - fun delete_nonExistent_shouldNotInsertLocalChange() { - database.delete(Patient::class.java, "nonexistent_patient") - assertThat(database.getAllLocalChanges().map { it.second } - .none { - it.type.equals(LocalChangeEntity.Type.DELETE) && - it.resourceId.equals("nonexistent_patient") - } - ).isTrue() - } + @Test + fun delete_nonExistent_shouldNotInsertLocalChange() { + database.delete(Patient::class.java, "nonexistent_patient") + assertThat( + database.getAllLocalChanges().map { it.second }.none { + it.type.equals(LocalChangeEntity.Type.DELETE) && + it.resourceId.equals("nonexistent_patient") + } + ) + .isTrue() + } - @Test - fun deleteUpdates_shouldDeleteLocalChanges() { - var patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insert(patient) - patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") - database.update(patient) - services.parser.encodeResourceToString(patient) - val (token, _) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(patient.id) } - database.deleteUpdates(token) - assertThat(database.getAllLocalChanges() - .none { it.second.resourceId.equals(patient.id) }) - .isTrue() - } + @Test + fun deleteUpdates_shouldDeleteLocalChanges() { + var patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insert(patient) + patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") + database.update(patient) + services.parser.encodeResourceToString(patient) + val (token, _) = + database.getAllLocalChanges().single { it.second.resourceId.equals(patient.id) } + database.deleteUpdates(token) + assertThat(database.getAllLocalChanges().none { it.second.resourceId.equals(patient.id) }) + .isTrue() + } - @Test - fun insert_remoteResource_shouldNotInsertLocalChange() { - val patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insertRemote(patient) - assertThat(database.getAllLocalChanges().map { it.second } - .none { it.resourceId.equals(patient.id) }) - .isTrue() - } + @Test + fun insert_remoteResource_shouldNotInsertLocalChange() { + val patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insertRemote(patient) + assertThat( + database.getAllLocalChanges().map { it.second }.none { it.resourceId.equals(patient.id) } + ) + .isTrue() + } - @Test - fun insertAll_remoteResources_shouldNotInsertAnyLocalChange() { - val patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insertAllRemote(listOf(patient, TEST_PATIENT_2)) - assertThat( - database.getAllLocalChanges() - .map { it.second } - .none { it.resourceId in listOf(patient.id, TEST_PATIENT_2_ID) } - ).isTrue() - } + @Test + fun insertAll_remoteResources_shouldNotInsertAnyLocalChange() { + val patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insertAllRemote(listOf(patient, TEST_PATIENT_2)) + assertThat( + database.getAllLocalChanges().map { it.second }.none { + it.resourceId in listOf(patient.id, TEST_PATIENT_2_ID) + } + ) + .isTrue() + } - @Test - fun update_remoteResource_readSquashedChanges_shouldReturnPatch() { - val patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insertRemote(patient) - val updatedPatient = - testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") - val updatePatch = testingUtils.readJsonArrayFromFile("/update_patch_1.json") - database.update(updatedPatient) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(patient.id) }.second - assertThat(type).isEqualTo(LocalChangeEntity.Type.UPDATE) - assertThat(resourceId).isEqualTo(patient.id) - assertThat(resourceType).isEqualTo(patient.resourceType.name) - testingUtils.assertJsonArrayEqualsIgnoringOrder(JSONArray(payload), updatePatch) - } + @Test + fun update_remoteResource_readSquashedChanges_shouldReturnPatch() { + val patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insertRemote(patient) + val updatedPatient = + testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") + val updatePatch = testingUtils.readJsonArrayFromFile("/update_patch_1.json") + database.update(updatedPatient) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().single { it.second.resourceId.equals(patient.id) }.second + assertThat(type).isEqualTo(LocalChangeEntity.Type.UPDATE) + assertThat(resourceId).isEqualTo(patient.id) + assertThat(resourceType).isEqualTo(patient.resourceType.name) + testingUtils.assertJsonArrayEqualsIgnoringOrder(JSONArray(payload), updatePatch) + } - @Test - fun updateTwice_remoteResource_readSquashedChanges_shouldReturnMergedPatch() { - var patient: Patient = - testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - database.insertRemote(patient) - patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") - database.update(patient) - patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_2.json") - database.update(patient) - val updatePatch = testingUtils.readJsonArrayFromFile("/update_patch_2.json") - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .single { it.second.resourceId.equals(patient.id) }.second - assertThat(type).isEqualTo(LocalChangeEntity.Type.UPDATE) - assertThat(resourceId).isEqualTo(patient.id) - assertThat(resourceType).isEqualTo(patient.resourceType.name) - testingUtils.assertJsonArrayEqualsIgnoringOrder(JSONArray(payload), updatePatch) - } + @Test + fun updateTwice_remoteResource_readSquashedChanges_shouldReturnMergedPatch() { + var patient: Patient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + database.insertRemote(patient) + patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_1.json") + database.update(patient) + patient = testingUtils.readFromFile(Patient::class.java, "/update_test_patient_2.json") + database.update(patient) + val updatePatch = testingUtils.readJsonArrayFromFile("/update_patch_2.json") + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().single { it.second.resourceId.equals(patient.id) }.second + assertThat(type).isEqualTo(LocalChangeEntity.Type.UPDATE) + assertThat(resourceId).isEqualTo(patient.id) + assertThat(resourceType).isEqualTo(patient.resourceType.name) + testingUtils.assertJsonArrayEqualsIgnoringOrder(JSONArray(payload), updatePatch) + } - @Test - fun delete_remoteResource_shouldReturnDeleteLocalChange() { - database.insertRemote(TEST_PATIENT_2) - database.delete(Patient::class.java, TEST_PATIENT_2_ID) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .map { it.second } - .single { it.resourceId.equals(TEST_PATIENT_2_ID) } - assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) - assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) - assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) - assertThat(payload).isEmpty() - } + @Test + fun delete_remoteResource_shouldReturnDeleteLocalChange() { + database.insertRemote(TEST_PATIENT_2) + database.delete(Patient::class.java, TEST_PATIENT_2_ID) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().map { it.second }.single { + it.resourceId.equals(TEST_PATIENT_2_ID) + } + assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) + assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) + assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) + assertThat(payload).isEmpty() + } - @Test - fun delete_remoteResource_updateResource_shouldReturnDeleteLocalChange() { - database.insertRemote(TEST_PATIENT_2) - TEST_PATIENT_2.setName(listOf(HumanName().addGiven("John").setFamily("Doe"))) - database.update(TEST_PATIENT_2) - TEST_PATIENT_2.setName(listOf(HumanName().addGiven("Jimmy").setFamily("Doe"))) - database.update(TEST_PATIENT_2) - database.delete(Patient::class.java, TEST_PATIENT_2_ID) - val (_, resourceType, resourceId, _, type, payload) = database.getAllLocalChanges() - .map { it.second } - .single { it.resourceId.equals(TEST_PATIENT_2_ID) } - assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) - assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) - assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) - assertThat(payload).isEmpty() - } + @Test + fun delete_remoteResource_updateResource_shouldReturnDeleteLocalChange() { + database.insertRemote(TEST_PATIENT_2) + TEST_PATIENT_2.setName(listOf(HumanName().addGiven("John").setFamily("Doe"))) + database.update(TEST_PATIENT_2) + TEST_PATIENT_2.setName(listOf(HumanName().addGiven("Jimmy").setFamily("Doe"))) + database.update(TEST_PATIENT_2) + database.delete(Patient::class.java, TEST_PATIENT_2_ID) + val (_, resourceType, resourceId, _, type, payload) = + database.getAllLocalChanges().map { it.second }.single { + it.resourceId.equals(TEST_PATIENT_2_ID) + } + assertThat(type).isEqualTo(LocalChangeEntity.Type.DELETE) + assertThat(resourceId).isEqualTo(TEST_PATIENT_2_ID) + assertThat(resourceType).isEqualTo(TEST_PATIENT_2.resourceType.name) + assertThat(payload).isEmpty() + } - private companion object { - const val TEST_PATIENT_1_ID = "test_patient_1" - val TEST_PATIENT_1 = Patient() + private companion object { + const val TEST_PATIENT_1_ID = "test_patient_1" + val TEST_PATIENT_1 = Patient() - init { - TEST_PATIENT_1.setId(TEST_PATIENT_1_ID) - TEST_PATIENT_1.setGender(Enumerations.AdministrativeGender.MALE) - } + init { + TEST_PATIENT_1.setId(TEST_PATIENT_1_ID) + TEST_PATIENT_1.setGender(Enumerations.AdministrativeGender.MALE) + } - const val TEST_PATIENT_2_ID = "test_patient_2" - val TEST_PATIENT_2 = Patient() + const val TEST_PATIENT_2_ID = "test_patient_2" + val TEST_PATIENT_2 = Patient() - init { - TEST_PATIENT_2.setId(TEST_PATIENT_2_ID) - TEST_PATIENT_2.setGender(Enumerations.AdministrativeGender.MALE) - } + init { + TEST_PATIENT_2.setId(TEST_PATIENT_2_ID) + TEST_PATIENT_2.setGender(Enumerations.AdministrativeGender.MALE) } + } } diff --git a/core/src/main/java/com/google/android/fhir/FhirEngine.kt b/core/src/main/java/com/google/android/fhir/FhirEngine.kt index f78bf02b41..fb3c7f74e5 100644 --- a/core/src/main/java/com/google/android/fhir/FhirEngine.kt +++ b/core/src/main/java/com/google/android/fhir/FhirEngine.kt @@ -23,61 +23,61 @@ import com.google.android.fhir.sync.SyncConfiguration import org.hl7.fhir.r4.model.Resource import org.opencds.cqf.cql.execution.EvaluationResult -/** The FHIR Engine interface that handles the local storage of FHIR resources. */ +/** The FHIR Engine interface that handles the local storage of FHIR resources. */ interface FhirEngine { - /** - * Saves a FHIR `resource` in the local storage. If the resource already exists, it will be - * overwritten - * - * @param The resource type which should be a subtype of [Resource]. - */ - fun save(resource: R) + /** + * Saves a FHIR `resource` in the local storage. If the resource already exists, it will be + * overwritten + * + * @param The resource type which should be a subtype of [Resource]. + */ + fun save(resource: R) - /** - * Saves a list of FHIR `resource` in the local storage. If any of the resources already - * exist, they will be overwritten. - * - * @param The resource type which should be a subtype of [Resource]. - */ - fun saveAll(resources: List) + /** + * Saves a list of FHIR `resource` in the local storage. If any of the resources already exist, + * they will be overwritten. + * + * @param The resource type which should be a subtype of [Resource]. + */ + fun saveAll(resources: List) - /** - * Updates a FHIR `resource` in the local storage. - * - * @param The resource type which should be a subtype of [Resource]. - */ - fun update(resource: R) + /** + * Updates a FHIR `resource` in the local storage. + * + * @param The resource type which should be a subtype of [Resource]. + */ + fun update(resource: R) - /** - * Returns a FHIR resource of type `clazz` with `id` from the local storage. - * - * @param The resource type which should be a subtype of [Resource]. - * @throws ResourceNotFoundException if the resource is not found - */ - @Throws(ResourceNotFoundException::class) - fun load(clazz: Class, id: String): R + /** + * Returns a FHIR resource of type `clazz` with `id` from the local storage. + * + * @param The resource type which should be a subtype of [Resource]. + * @throws ResourceNotFoundException if the resource is not found + */ + @Throws(ResourceNotFoundException::class) fun load(clazz: Class, id: String): R - /** - * Removes a FHIR resource of type `clazz` with `id` from the local storage. - * - * @param The resource type which should be a subtype of [Resource]. - */ - fun remove(clazz: Class, id: String) + /** + * Removes a FHIR resource of type `clazz` with `id` from the local storage. + * + * @param The resource type which should be a subtype of [Resource]. + */ + fun remove(clazz: Class, id: String) - /** Returns the result of a CQL evaluation provided with the ID of the library. */ - fun evaluateCql(libraryVersionId: String, context: String, expression: String): EvaluationResult + /** Returns the result of a CQL evaluation provided with the ID of the library. */ + fun evaluateCql(libraryVersionId: String, context: String, expression: String): EvaluationResult - /** Returns the entry point for [Search]. */ - fun search(): Search + /** Returns the entry point for [Search]. */ + fun search(): Search - /** - * One time sync. - * - * @param syncConfiguration - configuration of data that needs to be synchronised - */ - suspend fun sync(syncConfiguration: SyncConfiguration): Result + /** + * One time sync. + * + * @param syncConfiguration + * - configuration of data that needs to be synchronised + */ + suspend fun sync(syncConfiguration: SyncConfiguration): Result - suspend fun periodicSync(): Result + suspend fun periodicSync(): Result - fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration) + fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration) } diff --git a/core/src/main/java/com/google/android/fhir/FhirEngineBuilder.kt b/core/src/main/java/com/google/android/fhir/FhirEngineBuilder.kt index d0c084086d..c9a12cf7e6 100644 --- a/core/src/main/java/com/google/android/fhir/FhirEngineBuilder.kt +++ b/core/src/main/java/com/google/android/fhir/FhirEngineBuilder.kt @@ -20,38 +20,21 @@ import android.content.Context import com.google.android.fhir.sync.FhirDataSource import com.google.android.fhir.sync.PeriodicSyncConfiguration -/** - * The builder for [FhirEngine] instance - */ -class FhirEngineBuilder constructor( - dataSource: FhirDataSource, - context: Context -) { - private val services = FhirServices.builder(dataSource, context) +/** The builder for [FhirEngine] instance */ +class FhirEngineBuilder constructor(dataSource: FhirDataSource, context: Context) { + private val services = FhirServices.builder(dataSource, context) - /** - * Sets the database file name for the FhirEngine to use. - */ - fun databaseName(name: String) = apply { - services.databaseName(name) - } + /** Sets the database file name for the FhirEngine to use. */ + fun databaseName(name: String) = apply { services.databaseName(name) } - /** - * Instructs the FhirEngine to use an in memory database which can be useful for tests. - */ - internal fun inMemory() = apply { - services.inMemory() - } + /** Instructs the FhirEngine to use an in memory database which can be useful for tests. */ + internal fun inMemory() = apply { services.inMemory() } - /** - * Configures the FhirEngine periodic sync. - */ - fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply { - services.periodicSyncConfiguration(config) - } + /** Configures the FhirEngine periodic sync. */ + fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply { + services.periodicSyncConfiguration(config) + } - /** - * Builds a new instance of the [FhirEngine]. - */ - fun build() = services.build().fhirEngine + /** Builds a new instance of the [FhirEngine]. */ + fun build() = services.build().fhirEngine } diff --git a/core/src/main/java/com/google/android/fhir/FhirServices.kt b/core/src/main/java/com/google/android/fhir/FhirServices.kt index 06ee825f26..eae1585a21 100644 --- a/core/src/main/java/com/google/android/fhir/FhirServices.kt +++ b/core/src/main/java/com/google/android/fhir/FhirServices.kt @@ -30,61 +30,44 @@ import com.google.android.fhir.sync.FhirDataSource import com.google.android.fhir.sync.PeriodicSyncConfiguration internal data class FhirServices( - val fhirEngine: FhirEngine, - val parser: IParser, - val database: Database + val fhirEngine: FhirEngine, + val parser: IParser, + val database: Database ) { - class Builder( - private val dataSource: FhirDataSource, - private val context: Context - ) { - private var databaseName: String? = "fhirEngine" - private var periodicSyncConfiguration: PeriodicSyncConfiguration? = null + class Builder(private val dataSource: FhirDataSource, private val context: Context) { + private var databaseName: String? = "fhirEngine" + private var periodicSyncConfiguration: PeriodicSyncConfiguration? = null - fun inMemory() = apply { - databaseName = null - } + fun inMemory() = apply { databaseName = null } - fun databaseName(name: String) = apply { - databaseName = name - } + fun databaseName(name: String) = apply { databaseName = name } - fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply { - periodicSyncConfiguration = config - } + fun periodicSyncConfiguration(config: PeriodicSyncConfiguration) = apply { + periodicSyncConfiguration = config + } - fun build(): FhirServices { - val parser = FhirContext.forR4().newJsonParser() - val db = DatabaseImpl( - context = context, - iParser = parser, - databaseName = databaseName - ) + fun build(): FhirServices { + val parser = FhirContext.forR4().newJsonParser() + val db = DatabaseImpl(context = context, iParser = parser, databaseName = databaseName) - val dataProvider = FhirEngineDataProvider.Factory.create(db) - val engine = FhirEngineImpl( - database = db, - search = SearchImpl(db), - libraryLoader = FhirEngineLibraryLoader(db), - dataProviderMap = mapOf("http://hl7.org/fhir" to dataProvider), - terminologyProvider = FhirEngineTerminologyProvider(), - periodicSyncConfiguration = periodicSyncConfiguration, - dataSource = dataSource, - context = context - ) - return FhirServices( - fhirEngine = engine, - parser = parser, - database = db - ) - } + val dataProvider = FhirEngineDataProvider.Factory.create(db) + val engine = + FhirEngineImpl( + database = db, + search = SearchImpl(db), + libraryLoader = FhirEngineLibraryLoader(db), + dataProviderMap = mapOf("http://hl7.org/fhir" to dataProvider), + terminologyProvider = FhirEngineTerminologyProvider(), + periodicSyncConfiguration = periodicSyncConfiguration, + dataSource = dataSource, + context = context + ) + return FhirServices(fhirEngine = engine, parser = parser, database = db) } + } - companion object { - @JvmStatic - fun builder( - dataSource: FhirDataSource, - context: Context - ) = Builder(dataSource, context) - } + companion object { + @JvmStatic + fun builder(dataSource: FhirDataSource, context: Context) = Builder(dataSource, context) + } } diff --git a/core/src/main/java/com/google/android/fhir/ResourceAlreadyExistsException.kt b/core/src/main/java/com/google/android/fhir/ResourceAlreadyExistsException.kt index eb0143c178..e88d8bc80b 100644 --- a/core/src/main/java/com/google/android/fhir/ResourceAlreadyExistsException.kt +++ b/core/src/main/java/com/google/android/fhir/ResourceAlreadyExistsException.kt @@ -17,8 +17,5 @@ package com.google.android.fhir /** Thrown to indicate that the resource already exists. */ -class ResourceAlreadyExistsException( - val type: String, - val id: String, - cause: Throwable -) : Exception("Resource with type $type and id $id already exists!", cause) +class ResourceAlreadyExistsException(val type: String, val id: String, cause: Throwable) : + Exception("Resource with type $type and id $id already exists!", cause) diff --git a/core/src/main/java/com/google/android/fhir/ResourceNotFoundException.kt b/core/src/main/java/com/google/android/fhir/ResourceNotFoundException.kt index 57845f6eb4..c216cc6e4b 100644 --- a/core/src/main/java/com/google/android/fhir/ResourceNotFoundException.kt +++ b/core/src/main/java/com/google/android/fhir/ResourceNotFoundException.kt @@ -17,8 +17,5 @@ package com.google.android.fhir /** Thrown to indicate that the requested resource is not found. */ -class ResourceNotFoundException( - val type: String, - val id: String, - cause: Throwable -) : Exception("Resource not found with type $type and id $id!", cause) +class ResourceNotFoundException(val type: String, val id: String, cause: Throwable) : + Exception("Resource not found with type $type and id $id!", cause) diff --git a/core/src/main/java/com/google/android/fhir/Util.kt b/core/src/main/java/com/google/android/fhir/Util.kt index c00460906a..1ee5c7bf2f 100644 --- a/core/src/main/java/com/google/android/fhir/Util.kt +++ b/core/src/main/java/com/google/android/fhir/Util.kt @@ -22,14 +22,11 @@ import java.time.format.DateTimeFormatter import java.util.Date import java.util.Locale -/** - * Utility function to format a [Date] object using the system's default locale. - */ +/** Utility function to format a [Date] object using the system's default locale. */ @SuppressLint("NewApi") internal fun Date.toTimeZoneString(): String { - val simpleDateFormat = DateTimeFormatter.ofPattern( - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", - Locale.getDefault() - ).withZone(ZoneId.systemDefault()) - return simpleDateFormat.format(this.toInstant()) + val simpleDateFormat = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.getDefault()) + .withZone(ZoneId.systemDefault()) + return simpleDateFormat.format(this.toInstant()) } diff --git a/core/src/main/java/com/google/android/fhir/cql/AndroidR4FhirModelResolver.kt b/core/src/main/java/com/google/android/fhir/cql/AndroidR4FhirModelResolver.kt index 74fddca477..704405c9ad 100644 --- a/core/src/main/java/com/google/android/fhir/cql/AndroidR4FhirModelResolver.kt +++ b/core/src/main/java/com/google/android/fhir/cql/AndroidR4FhirModelResolver.kt @@ -19,38 +19,39 @@ package com.google.android.fhir.cql import org.hl7.fhir.instance.model.api.IBase import org.opencds.cqf.cql.model.R4FhirModelResolver -/** An [R4FhirModelResolver] on Android. */ +/** An [R4FhirModelResolver] on Android. */ internal class AndroidR4FhirModelResolver : R4FhirModelResolver() { - override fun getContextPath(contextType: String?, targetType: String?): Any? { - var targetType: String? = targetType - if (targetType == null || contextType == null) { - return null - } - when (contextType) { - "Unspecified", "Population" -> return null - targetType -> return "id" - } - - // Workaround for the issue of target type incorrectly including a namespace URI prefix. - // TODO: remove this. - if (targetType.startsWith(NAMESPACE_URI_PREFIX)) { - targetType = targetType.substring(NAMESPACE_URI_PREFIX.length) - } - - val resourceDefinition = fhirContext.getResourceDefinition(targetType) - val theValue = this.createInstance(contextType) - val type = theValue.javaClass as Class - val children = resourceDefinition.children - return children.asSequence() - .mapNotNull { child -> innerGetContextPath(child, type) } - .firstOrNull() + override fun getContextPath(contextType: String?, targetType: String?): Any? { + var targetType: String? = targetType + if (targetType == null || contextType == null) { + return null + } + when (contextType) { + "Unspecified", "Population" -> return null + targetType -> return "id" } - companion object { - /** - * A prefix that is incorrectly included on Android due to the inconsistency of JSON - * deserialization. - */ - const val NAMESPACE_URI_PREFIX = "{http://hl7.org/fhir}" + // Workaround for the issue of target type incorrectly including a namespace URI prefix. + // TODO: remove this. + if (targetType.startsWith(NAMESPACE_URI_PREFIX)) { + targetType = targetType.substring(NAMESPACE_URI_PREFIX.length) } + + val resourceDefinition = fhirContext.getResourceDefinition(targetType) + val theValue = this.createInstance(contextType) + val type = theValue.javaClass as Class + val children = resourceDefinition.children + return children + .asSequence() + .mapNotNull { child -> innerGetContextPath(child, type) } + .firstOrNull() + } + + companion object { + /** + * A prefix that is incorrectly included on Android due to the inconsistency of JSON + * deserialization. + */ + const val NAMESPACE_URI_PREFIX = "{http://hl7.org/fhir}" + } } diff --git a/core/src/main/java/com/google/android/fhir/cql/FhirEngineDataProvider.kt b/core/src/main/java/com/google/android/fhir/cql/FhirEngineDataProvider.kt index e6dd6ec705..e73309f80a 100644 --- a/core/src/main/java/com/google/android/fhir/cql/FhirEngineDataProvider.kt +++ b/core/src/main/java/com/google/android/fhir/cql/FhirEngineDataProvider.kt @@ -22,18 +22,15 @@ import org.opencds.cqf.cql.model.ModelResolver import org.opencds.cqf.cql.retrieve.RetrieveProvider /** - * FHIR Engine's implementation of a [org.opencds.cqf.cql.data.DataProvider] which provides - * the [org.opencds.cqf.cql.execution.CqlEngine] required data to complete CQL evaluation. + * FHIR Engine's implementation of a [org.opencds.cqf.cql.data.DataProvider] which provides the + * [org.opencds.cqf.cql.execution.CqlEngine] required data to complete CQL evaluation. */ -internal class FhirEngineDataProvider internal constructor( - modelResolver: ModelResolver, - retrieveProvider: RetrieveProvider -) : CompositeDataProvider(modelResolver, retrieveProvider) { +internal class FhirEngineDataProvider +internal constructor(modelResolver: ModelResolver, retrieveProvider: RetrieveProvider) : + CompositeDataProvider(modelResolver, retrieveProvider) { - internal object Factory { - internal fun create(database: Database): FhirEngineDataProvider = FhirEngineDataProvider( - AndroidR4FhirModelResolver(), - FhirEngineRetrieveProvider(database) - ) - } + internal object Factory { + internal fun create(database: Database): FhirEngineDataProvider = + FhirEngineDataProvider(AndroidR4FhirModelResolver(), FhirEngineRetrieveProvider(database)) + } } diff --git a/core/src/main/java/com/google/android/fhir/cql/FhirEngineLibraryLoader.kt b/core/src/main/java/com/google/android/fhir/cql/FhirEngineLibraryLoader.kt index 8b7f2b4dc5..d35137d107 100644 --- a/core/src/main/java/com/google/android/fhir/cql/FhirEngineLibraryLoader.kt +++ b/core/src/main/java/com/google/android/fhir/cql/FhirEngineLibraryLoader.kt @@ -27,42 +27,47 @@ import org.opencds.cqf.cql.execution.LibraryLoader * FHIR Engine's implementation of [LibraryLoader] that loads a CQL/ELM library for the [ ] to use. */ internal class FhirEngineLibraryLoader(private val database: Database) : LibraryLoader { - /** Cached libraries. */ - private val _libraryMap = mutableMapOf() - val libraryMap: Map - get() = _libraryMap + /** Cached libraries. */ + private val _libraryMap = mutableMapOf() + val libraryMap: Map + get() = _libraryMap - override fun load(libraryIdentifier: VersionedIdentifier): Library { - val matchedLibrary = libraryMap - .asSequence() - .filter { - // TODO: Change this to an exact match once the libraries are correctly indexed by name - it.key.contains(libraryIdentifier.id) - } - .map { it.value } - .firstOrNull() - if (matchedLibrary != null) return matchedLibrary - val fhirLibrary = database.searchByString( - org.hl7.fhir.r4.model.Library::class.java, - LIBRARY_NAME_INDEX, - libraryIdentifier.id - ) - // TODO: remove the assumption that there will be only one FHIR library resource which has one - // content element. - val stringReader = String(fhirLibrary.first().content.first().data).reader() - return try { - val cqlLibrary = JsonCqlLibraryReader.read(stringReader) - _libraryMap[libraryIdentifier.id] = cqlLibrary - cqlLibrary - } catch (e: IOException) { - // TODO: Replace this with a logger call - e.printStackTrace() - throw RuntimeException(e) + override fun load(libraryIdentifier: VersionedIdentifier): Library { + val matchedLibrary = + libraryMap + .asSequence() + .filter { + // TODO: Change this to an exact match once the libraries are correctly indexed + // by + // name + it.key.contains(libraryIdentifier.id) } + .map { it.value } + .firstOrNull() + if (matchedLibrary != null) return matchedLibrary + val fhirLibrary = + database.searchByString( + org.hl7.fhir.r4.model.Library::class.java, + LIBRARY_NAME_INDEX, + libraryIdentifier.id + ) + // TODO: remove the assumption that there will be only one FHIR library resource which has + // one + // content element. + val stringReader = String(fhirLibrary.first().content.first().data).reader() + return try { + val cqlLibrary = JsonCqlLibraryReader.read(stringReader) + _libraryMap[libraryIdentifier.id] = cqlLibrary + cqlLibrary + } catch (e: IOException) { + // TODO: Replace this with a logger call + e.printStackTrace() + throw RuntimeException(e) } + } - companion object { - /** The index for library name. */ - private const val LIBRARY_NAME_INDEX = "Library.name" - } + companion object { + /** The index for library name. */ + private const val LIBRARY_NAME_INDEX = "Library.name" + } } diff --git a/core/src/main/java/com/google/android/fhir/cql/FhirEngineRetrieveProvider.kt b/core/src/main/java/com/google/android/fhir/cql/FhirEngineRetrieveProvider.kt index c5e0efe383..b1c7d10ca9 100644 --- a/core/src/main/java/com/google/android/fhir/cql/FhirEngineRetrieveProvider.kt +++ b/core/src/main/java/com/google/android/fhir/cql/FhirEngineRetrieveProvider.kt @@ -23,52 +23,52 @@ import org.opencds.cqf.cql.runtime.Code import org.opencds.cqf.cql.runtime.Interval /** - * FHIR Engine's implementation of a [org.opencds.cqf.cql.retrieve.RetrieveProvider] which - * provides the [org.opencds.cqf.cql.execution.CqlEngine] required FHIR resources to complete - * CQL evaluation. + * FHIR Engine's implementation of a [org.opencds.cqf.cql.retrieve.RetrieveProvider] which provides + * the [org.opencds.cqf.cql.execution.CqlEngine] required FHIR resources to complete CQL evaluation. * - * - * Note: must be used in conjunction with a [org.opencds.cqf.cql.model.ModelResolver] for - * HAPI FHIR resources. + * Note: must be used in conjunction with a [org.opencds.cqf.cql.model.ModelResolver] for HAPI FHIR + * resources. */ internal class FhirEngineRetrieveProvider(private val database: Database) : RetrieveProvider { - override fun retrieve( - context: String, - contextPath: String, - contextValue: Any, - dataType: String, - templateId: String, - codePath: String, - codes: Iterable, - valueSet: String, - datePath: String, - dateLowPath: String, - dateHighPath: String, - dateRange: Interval - ): Iterable { - val codeList = codes.toList() - return when (codeList.size) { - 0 -> database.searchByReference( - clazz = getResourceClass(dataType), - reference = "$dataType.$contextPath", - value = if ((contextValue as String).isEmpty()) "" else "$context/$contextValue" - ) - 1 -> { - val code = codeList[0] - database.searchByReferenceAndCode( - clazz = getResourceClass(dataType), - reference = "$dataType.$contextPath", - referenceValue = if ((contextValue as String).isEmpty()) { - "" - } else { - "$context/$contextValue" - }, - code = "$dataType.$codePath", - codeSystem = code.system, - codeValue = code.code - ) - } - else -> emptyList() - } + override fun retrieve( + context: String, + contextPath: String, + contextValue: Any, + dataType: String, + templateId: String, + codePath: String, + codes: Iterable, + valueSet: String, + datePath: String, + dateLowPath: String, + dateHighPath: String, + dateRange: Interval + ): Iterable { + val codeList = codes.toList() + return when (codeList.size) { + 0 -> + database.searchByReference( + clazz = getResourceClass(dataType), + reference = "$dataType.$contextPath", + value = if ((contextValue as String).isEmpty()) "" else "$context/$contextValue" + ) + 1 -> { + val code = codeList[0] + database.searchByReferenceAndCode( + clazz = getResourceClass(dataType), + reference = "$dataType.$contextPath", + referenceValue = + if ((contextValue as String).isEmpty()) { + "" + } else { + "$context/$contextValue" + }, + code = "$dataType.$codePath", + codeSystem = code.system, + codeValue = code.code + ) + } + else -> emptyList() } + } } diff --git a/core/src/main/java/com/google/android/fhir/cql/FhirEngineTerminologyProvider.kt b/core/src/main/java/com/google/android/fhir/cql/FhirEngineTerminologyProvider.kt index 5902aa110a..6ee5e0a7ee 100644 --- a/core/src/main/java/com/google/android/fhir/cql/FhirEngineTerminologyProvider.kt +++ b/core/src/main/java/com/google/android/fhir/cql/FhirEngineTerminologyProvider.kt @@ -21,11 +21,11 @@ import org.opencds.cqf.cql.terminology.CodeSystemInfo import org.opencds.cqf.cql.terminology.TerminologyProvider import org.opencds.cqf.cql.terminology.ValueSetInfo -/** Fhir Engine's implementation of [TerminologyProvider]. */ +/** Fhir Engine's implementation of [TerminologyProvider]. */ internal class FhirEngineTerminologyProvider : TerminologyProvider { - override fun `in`(code: Code, valueSet: ValueSetInfo): Boolean = false + override fun `in`(code: Code, valueSet: ValueSetInfo): Boolean = false - override fun expand(valueSet: ValueSetInfo): Iterable? = null + override fun expand(valueSet: ValueSetInfo): Iterable? = null - override fun lookup(code: Code, codeSystem: CodeSystemInfo): Code? = null + override fun lookup(code: Code, codeSystem: CodeSystemInfo): Code? = null } diff --git a/core/src/main/java/com/google/android/fhir/db/Database.kt b/core/src/main/java/com/google/android/fhir/db/Database.kt index ccd430125b..147613863e 100644 --- a/core/src/main/java/com/google/android/fhir/db/Database.kt +++ b/core/src/main/java/com/google/android/fhir/db/Database.kt @@ -23,150 +23,140 @@ import com.google.android.fhir.search.impl.Query import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType -/** The interface for the FHIR resource database. */ +/** The interface for the FHIR resource database. */ interface Database { - /** - * Inserts the local origin `resource` into the FHIR resource database. If the resource already - * exists, it will be overwritten. - * - * @param The resource type - */ - fun insert(resource: R) - - /** - * Inserts the remote origin `resource` into the FHIR resource database. If the resource already - * exists, it will be overwritten. - * - * @param The resource type - */ - fun insertRemote(resource: R) - - /** - * Inserts a list of local `resources` into the FHIR resource database. If any of the resources - * already exists, it will be overwritten. - * - * @param The resource type - */ - fun insertAll(resources: List) - - /** - * Inserts a list of remote `resources` into the FHIR resource database. If any of the resources - * already exists, it will be overwritten. - * - * @param The resource type - */ - fun insertAllRemote(resources: List) - - /** - * Updates the `resource` in the FHIR resource database. If the resource does not already - * exist, then it will not be created. - * - * @param The resource type - */ - fun update(resource: R) - - /** - * Selects the FHIR resource of type `clazz` with `id`. - * - * @param The resource type - * @throws ResourceNotFoundInDbException if the resource is not found in the database - */ - @Throws(ResourceNotFoundInDbException::class) - fun select(clazz: Class, id: String): R - - /** - * Return the last update data of a resource based on the resource type. - * If no resource of [resourceType] is inserted, return `null`. - * @param resourceType The resource type - */ - suspend fun lastUpdate(resourceType: ResourceType): String? - - /** - * Insert a resource that was syncronised. - * - * @param syncedResourceEntity The synced resource - */ - suspend fun insertSyncedResources( - syncedResourceEntity: SyncedResourceEntity, - resources: List - ) - - /** - * Deletes the FHIR resource of type `clazz` with `id`. - * - * @param The resource type - */ - fun delete(clazz: Class, id: String) - - /** - * Returns a [List] of [Resource]s that are of type `clazz` and have `reference` with `value`. - * - * For example, a search for [org.hl7.fhir.r4.model.Observation]s with `reference` - * 'subject' and `value` 'Patient/1' will return all observations associated with the - * particular patient. - */ - fun searchByReference( - clazz: Class, - reference: String, - value: String - ): List - - /** - * Returns a [List] of [Resource]s that are of type `clazz` and have `string` with `value`. - * - * For example, a search for [org.hl7.fhir.r4.model.Patient]s with `string` 'given' - * and `value` 'Tom' will return all patients with a given name Tom. - */ - fun searchByString( - clazz: Class, - string: String, - value: String - ): List - - /** - * Returns a [List] of [Resource]s that are of type `clazz` and have `code` with `system` and - * `value`. - * - * For example, a search for [org.hl7.fhir.r4.model.Observation]s with `code` 'code', `system` - * 'http://openmrs.org/concepts' and `value` '1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' will return - * all observations with the given code. - */ - fun searchByCode( - clazz: Class, - code: String, - system: String, - value: String - ): List - - /** - * Returns a [List] of [Resource]s that are of type `clazz` and have `reference` with `value` - * and `code` with `system` and `value`. - * - * For example, a search for [org.hl7.fhir.r4.model.Observation]s with `reference` - * 'subject' and `value` 'Patient/1' as well as with `code` 'code', `system` - * 'http://openmrs.org/concepts' and `value` '1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' will return - * all observations associated with the particular patient by reference and with the given code. - */ - fun searchByReferenceAndCode( - clazz: Class, - reference: String, - referenceValue: String, - code: String, - codeSystem: String, - codeValue: String - ): List - - fun search(query: Query): List - - /** - * Retrieves all [LocalChangeEntity]s for all [Resource]s, which can be used to update the remote - * FHIR server. Each [resource] will have at most one [LocalChangeEntity] (multiple changes are - * squashed). - */ - fun getAllLocalChanges(): List> - - /** - * Remove the [LocalChangeEntity]s with given ids. Call this after a successful sync. - */ - fun deleteUpdates(token: LocalChangeToken) + /** + * Inserts the local origin `resource` into the FHIR resource database. If the resource already + * exists, it will be overwritten. + * + * @param The resource type + */ + fun insert(resource: R) + + /** + * Inserts the remote origin `resource` into the FHIR resource database. If the resource already + * exists, it will be overwritten. + * + * @param The resource type + */ + fun insertRemote(resource: R) + + /** + * Inserts a list of local `resources` into the FHIR resource database. If any of the resources + * already exists, it will be overwritten. + * + * @param The resource type + */ + fun insertAll(resources: List) + + /** + * Inserts a list of remote `resources` into the FHIR resource database. If any of the resources + * already exists, it will be overwritten. + * + * @param The resource type + */ + fun insertAllRemote(resources: List) + + /** + * Updates the `resource` in the FHIR resource database. If the resource does not already exist, + * then it will not be created. + * + * @param The resource type + */ + fun update(resource: R) + + /** + * Selects the FHIR resource of type `clazz` with `id`. + * + * @param The resource type + * @throws ResourceNotFoundInDbException if the resource is not found in the database + */ + @Throws(ResourceNotFoundInDbException::class) + fun select(clazz: Class, id: String): R + + /** + * Return the last update data of a resource based on the resource type. If no resource of + * [resourceType] is inserted, return `null`. + * @param resourceType The resource type + */ + suspend fun lastUpdate(resourceType: ResourceType): String? + + /** + * Insert a resource that was syncronised. + * + * @param syncedResourceEntity The synced resource + */ + suspend fun insertSyncedResources( + syncedResourceEntity: SyncedResourceEntity, + resources: List + ) + + /** + * Deletes the FHIR resource of type `clazz` with `id`. + * + * @param The resource type + */ + fun delete(clazz: Class, id: String) + + /** + * Returns a [List] of [Resource] s that are of type `clazz` and have `reference` with `value`. + * + * For example, a search for [org.hl7.fhir.r4.model.Observation] s with `reference` 'subject' and + * `value` 'Patient/1' will return all observations associated with the particular patient. + */ + fun searchByReference(clazz: Class, reference: String, value: String): List + + /** + * Returns a [List] of [Resource] s that are of type `clazz` and have `string` with `value`. + * + * For example, a search for [org.hl7.fhir.r4.model.Patient] s with `string` 'given' and `value` + * 'Tom' will return all patients with a given name Tom. + */ + fun searchByString(clazz: Class, string: String, value: String): List + + /** + * Returns a [List] of [Resource] s that are of type `clazz` and have `code` with `system` and + * `value`. + * + * For example, a search for [org.hl7.fhir.r4.model.Observation] s with `code` 'code', `system` + * 'http://openmrs.org/concepts' and `value` '1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' will return + * all observations with the given code. + */ + fun searchByCode( + clazz: Class, + code: String, + system: String, + value: String + ): List + + /** + * Returns a [List] of [Resource] s that are of type `clazz` and have `reference` with `value` and + * `code` with `system` and `value`. + * + * For example, a search for [org.hl7.fhir.r4.model.Observation] s with `reference` 'subject' and + * `value` 'Patient/1' as well as with `code` 'code', `system` 'http://openmrs.org/concepts' and + * `value` '1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' will return all observations associated with the + * particular patient by reference and with the given code. + */ + fun searchByReferenceAndCode( + clazz: Class, + reference: String, + referenceValue: String, + code: String, + codeSystem: String, + codeValue: String + ): List + + fun search(query: Query): List + + /** + * Retrieves all [LocalChangeEntity] s for all [Resource] s, which can be used to update the + * remote FHIR server. Each [resource] will have at most one + * [LocalChangeEntity](multiple changes + * are squashed). + */ + fun getAllLocalChanges(): List> + + /** Remove the [LocalChangeEntity] s with given ids. Call this after a successful sync. */ + fun deleteUpdates(token: LocalChangeToken) } diff --git a/core/src/main/java/com/google/android/fhir/db/ResourceAlreadyExistsInDbException.kt b/core/src/main/java/com/google/android/fhir/db/ResourceAlreadyExistsInDbException.kt index c6258ef999..b91f7e754a 100644 --- a/core/src/main/java/com/google/android/fhir/db/ResourceAlreadyExistsInDbException.kt +++ b/core/src/main/java/com/google/android/fhir/db/ResourceAlreadyExistsInDbException.kt @@ -17,11 +17,5 @@ package com.google.android.fhir.db /** Exception thrown to indicate that the resource already exists in the database. */ -class ResourceAlreadyExistsInDbException( - val type: String, - val id: String, - cause: Throwable -) : Exception( - "Resource with type $type and id $id already exists!", - cause -) +class ResourceAlreadyExistsInDbException(val type: String, val id: String, cause: Throwable) : + Exception("Resource with type $type and id $id already exists!", cause) diff --git a/core/src/main/java/com/google/android/fhir/db/ResourceNotFoundInDbException.kt b/core/src/main/java/com/google/android/fhir/db/ResourceNotFoundInDbException.kt index 23475be7fb..fe68c0e573 100644 --- a/core/src/main/java/com/google/android/fhir/db/ResourceNotFoundInDbException.kt +++ b/core/src/main/java/com/google/android/fhir/db/ResourceNotFoundInDbException.kt @@ -17,7 +17,5 @@ package com.google.android.fhir.db /** Exception thrown to indicate that the requested resource is not found in the database. */ -class ResourceNotFoundInDbException( - val type: String, - val id: String -) : Exception("Resource not found with type $type and id $id!") +class ResourceNotFoundInDbException(val type: String, val id: String) : + Exception("Resource not found with type $type and id $id!") diff --git a/core/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt b/core/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt index 79ed7d18d0..6486e339c5 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/DatabaseImpl.kt @@ -31,171 +31,154 @@ import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType /** - * The implementation for the persistence layer using Room. - * See docs for [com.google.android.fhir.db.Database] for the API docs. + * The implementation for the persistence layer using Room. See docs for + * [com.google.android.fhir.db.Database] for the API docs. */ -internal class DatabaseImpl( +internal class DatabaseImpl(context: Context, private val iParser: IParser, databaseName: String?) : + com.google.android.fhir.db.Database { + constructor( context: Context, - private val iParser: IParser, - databaseName: String? -) : com.google.android.fhir.db.Database { - constructor( - context: Context, - iParser: IParser - ) : this( - context = context, - iParser = iParser, - databaseName = DEFAULT_DATABASE_NAME) - - val builder = if (databaseName == null) { - Room.inMemoryDatabaseBuilder(context, ResourceDatabase::class.java) - } else { - Room.databaseBuilder(context, ResourceDatabase::class.java, databaseName) - } - val db = builder - // TODO https://github.com/jingtang10/fhir-engine/issues/32 - // don't allow main thread queries - .allowMainThreadQueries() - .build() - val resourceDao by lazy { - db.resourceDao().also { - it.iParser = iParser - } - } - val syncedResourceDao = db.syncedResourceDao() - val localChangeDao = db.localChangeDao().also { - it.iParser = iParser - } - - @Transaction - override fun insert(resource: R) { - resourceDao.insert(resource) - localChangeDao.addInsert(resource) - } - - override fun insertRemote(resource: R) { - resourceDao.insert(resource) - } - - @Transaction - override fun insertAll(resources: List) { - resourceDao.insertAll(resources) - localChangeDao.addInsertAll(resources) - } - - override fun insertAllRemote(resources: List) { - resourceDao.insertAll(resources) - } - - @Transaction - override fun update(resource: R) { - val oldResource = select(resource.javaClass, resource.id) - resourceDao.update(resource) - localChangeDao.addUpdate(oldResource, resource) - } - - override fun select(clazz: Class, id: String): R { - val type = getResourceType(clazz) - return resourceDao.getResource( - resourceId = id, - resourceType = type - )?.let { - iParser.parseResource(clazz, it) - } ?: throw ResourceNotFoundInDbException(type.name, id) - } - - override suspend fun lastUpdate(resourceType: ResourceType): String? { - return syncedResourceDao.getLastUpdate(resourceType) - } - - @Transaction - override suspend fun insertSyncedResources( - syncedResourceEntity: SyncedResourceEntity, - resources: List - ) { - syncedResourceDao.insert(syncedResourceEntity) - insertAll(resources) - } - - @Transaction - override fun delete(clazz: Class, id: String) { - val type = getResourceType(clazz) - val rowsDeleted = resourceDao.deleteResource( - resourceId = id, - resourceType = type - ) - if (rowsDeleted > 0) localChangeDao.addDelete(resourceId = id, resourceType = type) - } - - override fun searchByReference( - clazz: Class, - reference: String, - value: String - ): List { - return resourceDao.getResourceByReferenceIndex( - getResourceType(clazz).name, reference, value) - .map { iParser.parseResource(it) as R } - } - - override fun searchByString( - clazz: Class, - string: String, - value: String - ): List { - return resourceDao.getResourceByStringIndex( - resourceType = getResourceType(clazz).name, - indexPath = string, - indexValue = value - ).map { iParser.parseResource(it) as R } - } + iParser: IParser + ) : this(context = context, iParser = iParser, databaseName = DEFAULT_DATABASE_NAME) - override fun searchByCode( - clazz: Class, - code: String, - system: String, - value: String - ): List { - return resourceDao.getResourceByCodeIndex( - resourceType = getResourceType(clazz).name, - indexPath = code, - indexSystem = system, - indexValue = value - ).map { iParser.parseResource(it) as R } - } - - override fun searchByReferenceAndCode( - clazz: Class, - reference: String, - referenceValue: String, - code: String, - codeSystem: String, - codeValue: String - ): List { - val refs = searchByReference(clazz, reference, referenceValue).map { it.id } - return searchByCode(clazz, code, codeSystem, codeValue).filter { refs.contains(it.id) } - } - - override fun search(query: Query): List = - resourceDao.getResources(query.getSupportSQLiteQuery()) - .map { iParser.parseResource(it) as R } - - /** - * @returns a list of pairs. Each pair is a token + squashed local change. Each token is a list - * of [LocalChangeEntity.id]s of rows of the [LocalChangeEntity]. - */ - // TODO: create a data class for squashed local change and merge token in to it. - override fun getAllLocalChanges(): List> = - localChangeDao.getAllLocalChanges().groupBy { it.resourceId to it.resourceType } - .values - .map { - LocalChangeToken(it.map { it.id }) to LocalChangeUtils.squash(it) - } - - override fun deleteUpdates(token: LocalChangeToken) { - localChangeDao.discardLocalChanges(token) - } - - companion object { - private const val DEFAULT_DATABASE_NAME = "ResourceDatabase" - } + val builder = + if (databaseName == null) { + Room.inMemoryDatabaseBuilder(context, ResourceDatabase::class.java) + } else { + Room.databaseBuilder(context, ResourceDatabase::class.java, databaseName) + } + val db = + builder + // TODO https://github.com/jingtang10/fhir-engine/issues/32 + // don't allow main thread queries + .allowMainThreadQueries() + .build() + val resourceDao by lazy { db.resourceDao().also { it.iParser = iParser } } + val syncedResourceDao = db.syncedResourceDao() + val localChangeDao = db.localChangeDao().also { it.iParser = iParser } + + @Transaction + override fun insert(resource: R) { + resourceDao.insert(resource) + localChangeDao.addInsert(resource) + } + + override fun insertRemote(resource: R) { + resourceDao.insert(resource) + } + + @Transaction + override fun insertAll(resources: List) { + resourceDao.insertAll(resources) + localChangeDao.addInsertAll(resources) + } + + override fun insertAllRemote(resources: List) { + resourceDao.insertAll(resources) + } + + @Transaction + override fun update(resource: R) { + val oldResource = select(resource.javaClass, resource.id) + resourceDao.update(resource) + localChangeDao.addUpdate(oldResource, resource) + } + + override fun select(clazz: Class, id: String): R { + val type = getResourceType(clazz) + return resourceDao.getResource(resourceId = id, resourceType = type)?.let { + iParser.parseResource(clazz, it) + } + ?: throw ResourceNotFoundInDbException(type.name, id) + } + + override suspend fun lastUpdate(resourceType: ResourceType): String? { + return syncedResourceDao.getLastUpdate(resourceType) + } + + @Transaction + override suspend fun insertSyncedResources( + syncedResourceEntity: SyncedResourceEntity, + resources: List + ) { + syncedResourceDao.insert(syncedResourceEntity) + insertAll(resources) + } + + @Transaction + override fun delete(clazz: Class, id: String) { + val type = getResourceType(clazz) + val rowsDeleted = resourceDao.deleteResource(resourceId = id, resourceType = type) + if (rowsDeleted > 0) localChangeDao.addDelete(resourceId = id, resourceType = type) + } + + override fun searchByReference( + clazz: Class, + reference: String, + value: String + ): List { + return resourceDao.getResourceByReferenceIndex(getResourceType(clazz).name, reference, value) + .map { iParser.parseResource(it) as R } + } + + override fun searchByString( + clazz: Class, + string: String, + value: String + ): List { + return resourceDao.getResourceByStringIndex( + resourceType = getResourceType(clazz).name, + indexPath = string, + indexValue = value + ) + .map { iParser.parseResource(it) as R } + } + + override fun searchByCode( + clazz: Class, + code: String, + system: String, + value: String + ): List { + return resourceDao.getResourceByCodeIndex( + resourceType = getResourceType(clazz).name, + indexPath = code, + indexSystem = system, + indexValue = value + ) + .map { iParser.parseResource(it) as R } + } + + override fun searchByReferenceAndCode( + clazz: Class, + reference: String, + referenceValue: String, + code: String, + codeSystem: String, + codeValue: String + ): List { + val refs = searchByReference(clazz, reference, referenceValue).map { it.id } + return searchByCode(clazz, code, codeSystem, codeValue).filter { refs.contains(it.id) } + } + + override fun search(query: Query): List = + resourceDao.getResources(query.getSupportSQLiteQuery()).map { iParser.parseResource(it) as R } + + /** + * @returns a list of pairs. Each pair is a token + squashed local change. Each token is a list of + * [LocalChangeEntity.id] s of rows of the [LocalChangeEntity]. + */ + // TODO: create a data class for squashed local change and merge token in to it. + override fun getAllLocalChanges(): List> = + localChangeDao.getAllLocalChanges().groupBy { it.resourceId to it.resourceType }.values.map { + LocalChangeToken(it.map { it.id }) to LocalChangeUtils.squash(it) + } + + override fun deleteUpdates(token: LocalChangeToken) { + localChangeDao.discardLocalChanges(token) + } + + companion object { + private const val DEFAULT_DATABASE_NAME = "ResourceDatabase" + } } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/DbTypeConverters.kt b/core/src/main/java/com/google/android/fhir/db/impl/DbTypeConverters.kt index 81802b24e9..d7991527c0 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/DbTypeConverters.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/DbTypeConverters.kt @@ -24,61 +24,52 @@ import java.util.Calendar import org.hl7.fhir.r4.model.ResourceType /** - * Type converters for Room to persist ResourceType as a string. - * see: https://developer.android.com/training/data-storage/room/referencing-data + * Type converters for Room to persist ResourceType as a string. see: + * https://developer.android.com/training/data-storage/room/referencing-data */ internal object DbTypeConverters { - private val resourceTypeLookup = ResourceType.values().associateBy { it.name } + private val resourceTypeLookup = ResourceType.values().associateBy { it.name } - /** - * Converts a [ResourceType] into a String to be persisted in the database. This allows us to - * save [ResourceType] into the database while keeping it as the real type in entities. - */ - @JvmStatic - @TypeConverter - fun typeToString(resourceType: ResourceType) = resourceType.name + /** + * Converts a [ResourceType] into a String to be persisted in the database. This allows us to save + * [ResourceType] into the database while keeping it as the real type in entities. + */ + @JvmStatic @TypeConverter fun typeToString(resourceType: ResourceType) = resourceType.name - /** - * Converts a String into a [ResourceType]. Called when a query returns a [ResourceType]. - */ - @JvmStatic - @TypeConverter - fun stringToResourceType(data: String) = resourceTypeLookup[data] - ?: throw IllegalArgumentException("invalid resource type: $data") + /** Converts a String into a [ResourceType]. Called when a query returns a [ResourceType]. */ + @JvmStatic + @TypeConverter + fun stringToResourceType(data: String) = + resourceTypeLookup[data] ?: throw IllegalArgumentException("invalid resource type: $data") - @JvmStatic - @TypeConverter - fun bigDecimalToString(value: BigDecimal): String = value.toString() + @JvmStatic @TypeConverter fun bigDecimalToString(value: BigDecimal): String = value.toString() - @JvmStatic - @TypeConverter - fun stringToBigDecimal(value: String): BigDecimal = value.toBigDecimal() + @JvmStatic @TypeConverter fun stringToBigDecimal(value: String): BigDecimal = value.toBigDecimal() - @JvmStatic - @TypeConverter - fun temporalPrecisionToInt(temporalPrecision: TemporalPrecisionEnum): Int = - temporalPrecision.calendarConstant + @JvmStatic + @TypeConverter + fun temporalPrecisionToInt(temporalPrecision: TemporalPrecisionEnum): Int = + temporalPrecision.calendarConstant - @JvmStatic - @TypeConverter - fun intToTemporalPrecision(intTp: Int): TemporalPrecisionEnum { - return when (intTp) { - Calendar.YEAR -> TemporalPrecisionEnum.YEAR - Calendar.MONTH -> TemporalPrecisionEnum.MONTH - Calendar.DATE -> TemporalPrecisionEnum.DAY - Calendar.MINUTE -> TemporalPrecisionEnum.MINUTE - Calendar.SECOND -> TemporalPrecisionEnum.SECOND - Calendar.MILLISECOND -> TemporalPrecisionEnum.MILLI - else -> throw IllegalArgumentException("Unknown TemporalPrecision int $intTp") - } + @JvmStatic + @TypeConverter + fun intToTemporalPrecision(intTp: Int): TemporalPrecisionEnum { + return when (intTp) { + Calendar.YEAR -> TemporalPrecisionEnum.YEAR + Calendar.MONTH -> TemporalPrecisionEnum.MONTH + Calendar.DATE -> TemporalPrecisionEnum.DAY + Calendar.MINUTE -> TemporalPrecisionEnum.MINUTE + Calendar.SECOND -> TemporalPrecisionEnum.SECOND + Calendar.MILLISECOND -> TemporalPrecisionEnum.MILLI + else -> throw IllegalArgumentException("Unknown TemporalPrecision int $intTp") } + } - @JvmStatic - @TypeConverter - fun localChangeTypeToInt(updateType: LocalChangeEntity.Type): Int = updateType.value + @JvmStatic + @TypeConverter + fun localChangeTypeToInt(updateType: LocalChangeEntity.Type): Int = updateType.value - @JvmStatic - @TypeConverter - fun intToLocalChangeType(value: Int): LocalChangeEntity.Type = - LocalChangeEntity.Type.from(value) + @JvmStatic + @TypeConverter + fun intToLocalChangeType(value: Int): LocalChangeEntity.Type = LocalChangeEntity.Type.from(value) } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt b/core/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt index b3df1cc7a0..8d36c02cf9 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/ResourceDatabase.kt @@ -34,26 +34,24 @@ import com.google.android.fhir.db.impl.entities.TokenIndexEntity import com.google.android.fhir.db.impl.entities.UriIndexEntity @Database( - entities = [ - ResourceEntity::class, - StringIndexEntity::class, - ReferenceIndexEntity::class, - TokenIndexEntity::class, - QuantityIndexEntity::class, - UriIndexEntity::class, - DateIndexEntity::class, - NumberIndexEntity::class, - SyncedResourceEntity::class, - LocalChangeEntity::class - ], - version = 1, - exportSchema = false -) -@TypeConverters( - DbTypeConverters::class + entities = + [ + ResourceEntity::class, + StringIndexEntity::class, + ReferenceIndexEntity::class, + TokenIndexEntity::class, + QuantityIndexEntity::class, + UriIndexEntity::class, + DateIndexEntity::class, + NumberIndexEntity::class, + SyncedResourceEntity::class, + LocalChangeEntity::class], + version = 1, + exportSchema = false ) +@TypeConverters(DbTypeConverters::class) internal abstract class ResourceDatabase : RoomDatabase() { - abstract fun resourceDao(): ResourceDao - abstract fun syncedResourceDao(): SyncedResourceDao - abstract fun localChangeDao(): LocalChangeDao + abstract fun resourceDao(): ResourceDao + abstract fun syncedResourceDao(): SyncedResourceDao + abstract fun localChangeDao(): LocalChangeDao } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt b/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt index 106cfddb57..249a3193da 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeDao.kt @@ -30,122 +30,124 @@ import org.hl7.fhir.r4.model.ResourceType /** * Dao for local changes made to a resource. One row in LocalChangeEntity corresponds to one change - * e.g. an INSERT or UPDATE. The UPDATES (diffs) are stored as RFC 6902 JSON patches. - * When a resource needs to be synced, all corresponding LocalChanges are 'squashed' to create a - * a single LocalChangeEntity to sync with the server. + * e.g. an INSERT or UPDATE. The UPDATES (diffs) are stored as RFC 6902 JSON patches. When a + * resource needs to be synced, all corresponding LocalChanges are 'squashed' to create a a single + * LocalChangeEntity to sync with the server. */ @Dao internal abstract class LocalChangeDao { - lateinit var iParser: IParser - - @Insert - abstract fun addLocalChange(localChangeEntity: LocalChangeEntity) - - @Transaction - open fun addInsertAll(resources: List) { - resources.forEach { resource -> - addInsert(resource) - } - } - - fun addInsert(resource: Resource) { - val resourceId = resource.id - val resourceType = resource.resourceType - val timestamp = Date().toTimeZoneString() - val resourceString = iParser.encodeResourceToString(resource) - - addLocalChange( - LocalChangeEntity( - id = 0, - resourceType = resourceType.name, - resourceId = resourceId, - timestamp = timestamp, - type = Type.INSERT, - payload = resourceString - ) - ) - } - - fun addUpdate(oldResource: Resource, resource: Resource) { - val resourceId = resource.id - val resourceType = resource.resourceType - val timestamp = Date().toTimeZoneString() - - if (!localChangeIsEmpty(resourceId, resourceType) && - lastChangeType(resourceId, resourceType)!!.equals(Type.DELETE)) { - throw InvalidLocalChangeException( - "Unexpected DELETE when updating $resourceType/$resourceId. UPDATE failed." - ) - } - - addLocalChange( - LocalChangeEntity( - id = 0, - resourceType = resourceType.name, - resourceId = resourceId, - timestamp = timestamp, - type = Type.UPDATE, - payload = LocalChangeUtils.diff(iParser, oldResource, resource) - ) - ) + lateinit var iParser: IParser + + @Insert abstract fun addLocalChange(localChangeEntity: LocalChangeEntity) + + @Transaction + open fun addInsertAll(resources: List) { + resources.forEach { resource -> addInsert(resource) } + } + + fun addInsert(resource: Resource) { + val resourceId = resource.id + val resourceType = resource.resourceType + val timestamp = Date().toTimeZoneString() + val resourceString = iParser.encodeResourceToString(resource) + + addLocalChange( + LocalChangeEntity( + id = 0, + resourceType = resourceType.name, + resourceId = resourceId, + timestamp = timestamp, + type = Type.INSERT, + payload = resourceString + ) + ) + } + + fun addUpdate(oldResource: Resource, resource: Resource) { + val resourceId = resource.id + val resourceType = resource.resourceType + val timestamp = Date().toTimeZoneString() + + if (!localChangeIsEmpty(resourceId, resourceType) && + lastChangeType(resourceId, resourceType)!!.equals(Type.DELETE) + ) { + throw InvalidLocalChangeException( + "Unexpected DELETE when updating $resourceType/$resourceId. UPDATE failed." + ) } - fun addDelete(resourceId: String, resourceType: ResourceType) { - val timestamp = Date().toTimeZoneString() - addLocalChange( - LocalChangeEntity( - id = 0, - resourceType = resourceType.name, - resourceId = resourceId, - timestamp = timestamp, - type = Type.DELETE, - payload = "" - ) - ) - } + addLocalChange( + LocalChangeEntity( + id = 0, + resourceType = resourceType.name, + resourceId = resourceId, + timestamp = timestamp, + type = Type.UPDATE, + payload = LocalChangeUtils.diff(iParser, oldResource, resource) + ) + ) + } + + fun addDelete(resourceId: String, resourceType: ResourceType) { + val timestamp = Date().toTimeZoneString() + addLocalChange( + LocalChangeEntity( + id = 0, + resourceType = resourceType.name, + resourceId = resourceId, + timestamp = timestamp, + type = Type.DELETE, + payload = "" + ) + ) + } - @Query(""" + @Query( + """ SELECT type FROM LocalChangeEntity WHERE resourceId = :resourceId AND resourceType = :resourceType ORDER BY id ASC LIMIT 1 - """) - abstract fun lastChangeType(resourceId: String, resourceType: ResourceType): Type? + """ + ) + abstract fun lastChangeType(resourceId: String, resourceType: ResourceType): Type? - @Query(""" + @Query( + """ SELECT COUNT(type) FROM LocalChangeEntity WHERE resourceId = :resourceId AND resourceType = :resourceType LIMIT 1 - """) - abstract fun countLastChange(resourceId: String, resourceType: ResourceType): Int + """ + ) + abstract fun countLastChange(resourceId: String, resourceType: ResourceType): Int - private fun localChangeIsEmpty(resourceId: String, resourceType: ResourceType): Boolean = - countLastChange(resourceId, resourceType) == 0 + private fun localChangeIsEmpty(resourceId: String, resourceType: ResourceType): Boolean = + countLastChange(resourceId, resourceType) == 0 - @Query( - """ + @Query( + """ SELECT * FROM LocalChangeEntity ORDER BY LocalChangeEntity.id ASC""" - ) - abstract fun getAllLocalChanges(): List + ) + abstract fun getAllLocalChanges(): List - @Query( - """ + @Query( + """ DELETE FROM LocalChangeEntity WHERE LocalChangeEntity.id = (:id) """ - ) - abstract fun discardLocalChanges(id: Long) + ) + abstract fun discardLocalChanges(id: Long) - fun discardLocalChanges(token: LocalChangeToken) { - token.ids.forEach { discardLocalChanges(it) } - } + fun discardLocalChanges(token: LocalChangeToken) { + token.ids.forEach { discardLocalChanges(it) } + } - class InvalidLocalChangeException(message: String?) : Exception(message) + class InvalidLocalChangeException(message: String?) : Exception(message) } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeUtils.kt b/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeUtils.kt index 6a22450996..ac50632cc1 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeUtils.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/dao/LocalChangeUtils.kt @@ -30,99 +30,88 @@ import org.json.JSONObject object LocalChangeUtils { - /** - * Squash the changes by merging them two at a time. - */ - fun squash(localChangeEntities: List): LocalChangeEntity = - localChangeEntities.reduce { first, second -> mergeLocalChanges(first, second) } + /** Squash the changes by merging them two at a time. */ + fun squash(localChangeEntities: List): LocalChangeEntity = + localChangeEntities.reduce { first, second -> mergeLocalChanges(first, second) } - fun mergeLocalChanges(first: LocalChangeEntity, second: LocalChangeEntity): LocalChangeEntity { - val type: LocalChangeEntity.Type - val payload: String - when (second.type) { - LocalChangeEntity.Type.UPDATE -> when { - first.type.equals(LocalChangeEntity.Type.UPDATE) -> { - type = LocalChangeEntity.Type.UPDATE - payload = mergePatches(first.payload, second.payload) - } - first.type.equals(LocalChangeEntity.Type.INSERT) -> { - type = LocalChangeEntity.Type.INSERT - payload = applyPatch(first.payload, second.payload) - } - else -> { - throw IllegalArgumentException( - "Cannot merge local changes with type ${first.type} and ${second.type}." - ) - } - } - LocalChangeEntity.Type.DELETE -> { - type = LocalChangeEntity.Type.DELETE - payload = "" - } - LocalChangeEntity.Type.INSERT -> { - type = LocalChangeEntity.Type.INSERT - payload = second.payload - } + fun mergeLocalChanges(first: LocalChangeEntity, second: LocalChangeEntity): LocalChangeEntity { + val type: LocalChangeEntity.Type + val payload: String + when (second.type) { + LocalChangeEntity.Type.UPDATE -> + when { + first.type.equals(LocalChangeEntity.Type.UPDATE) -> { + type = LocalChangeEntity.Type.UPDATE + payload = mergePatches(first.payload, second.payload) + } + first.type.equals(LocalChangeEntity.Type.INSERT) -> { + type = LocalChangeEntity.Type.INSERT + payload = applyPatch(first.payload, second.payload) + } + else -> { + throw IllegalArgumentException( + "Cannot merge local changes with type ${first.type} and ${second.type}." + ) + } } - return LocalChangeEntity( - id = 0, - resourceId = second.resourceId, - resourceType = second.resourceType, - type = type, - payload = payload - ) + LocalChangeEntity.Type.DELETE -> { + type = LocalChangeEntity.Type.DELETE + payload = "" + } + LocalChangeEntity.Type.INSERT -> { + type = LocalChangeEntity.Type.INSERT + payload = second.payload + } } + return LocalChangeEntity( + id = 0, + resourceId = second.resourceId, + resourceType = second.resourceType, + type = type, + payload = payload + ) + } - /** Update a JSON object with a JSON patch (RFC 6902). */ - private fun applyPatch(resourceString: String, patchString: String): String { - val objectMapper = ObjectMapper() - val resourceJson = objectMapper.readValue(resourceString, JsonNode::class.java) - val patchJson = objectMapper.readValue(patchString, JsonPatch::class.java) - return patchJson.apply(resourceJson).toString() - } + /** Update a JSON object with a JSON patch (RFC 6902). */ + private fun applyPatch(resourceString: String, patchString: String): String { + val objectMapper = ObjectMapper() + val resourceJson = objectMapper.readValue(resourceString, JsonNode::class.java) + val patchJson = objectMapper.readValue(patchString, JsonPatch::class.java) + return patchJson.apply(resourceJson).toString() + } - /** Merge two JSON patch strings by concatenating their elements into a new JSON array. */ - private fun mergePatches(firstPatch: String, secondPatch: String): String { - // TODO: validate patches are RFC 6902 compliant JSON patches - val firstMap = JSONArray(firstPatch).patchMergeMap() - val secondMap = JSONArray(secondPatch).patchMergeMap() - firstMap.putAll(secondMap) - return JSONArray(firstMap.values).toString() - } + /** Merge two JSON patch strings by concatenating their elements into a new JSON array. */ + private fun mergePatches(firstPatch: String, secondPatch: String): String { + // TODO: validate patches are RFC 6902 compliant JSON patches + val firstMap = JSONArray(firstPatch).patchMergeMap() + val secondMap = JSONArray(secondPatch).patchMergeMap() + firstMap.putAll(secondMap) + return JSONArray(firstMap.values).toString() + } - /** Calculates the JSON patch between two [Resource]s. */ - internal fun diff(parser: IParser, source: Resource, target: Resource): String { - val objectMapper = ObjectMapper() - val jsonDiff = JsonDiff.asJson( - objectMapper.readValue( - parser.encodeResourceToString(source), - JsonNode::class.java - ), - objectMapper.readValue( - parser.encodeResourceToString(target), - JsonNode::class.java - ) - ) - if (jsonDiff.size() == 0) { - Log.i( - "ResourceDao", - "Target ${target.resourceType}/${target.id} is same as source." - ) - } - return jsonDiff.toString() + /** Calculates the JSON patch between two [Resource] s. */ + internal fun diff(parser: IParser, source: Resource, target: Resource): String { + val objectMapper = ObjectMapper() + val jsonDiff = + JsonDiff.asJson( + objectMapper.readValue(parser.encodeResourceToString(source), JsonNode::class.java), + objectMapper.readValue(parser.encodeResourceToString(target), JsonNode::class.java) + ) + if (jsonDiff.size() == 0) { + Log.i("ResourceDao", "Target ${target.resourceType}/${target.id} is same as source.") } + return jsonDiff.toString() + } - /** - * Creates a mutable map from operation type (e.g. add/remove) + property path to the entire - * operation containing the updated value. - * Two such maps can be merged using `Map.putAll()` to yield a minimal set of operations - * equivalent to individual patches. - */ - private fun JSONArray.patchMergeMap(): MutableMap, JSONObject> { - return (0 until this.length()) - .map { this.optJSONObject(it) } - .associateBy { - it.optString("op") to it.optString("path") - }.toMutableMap() - } + /** + * Creates a mutable map from operation type (e.g. add/remove) + property path to the entire + * operation containing the updated value. Two such maps can be merged using `Map.putAll()` to + * yield a minimal set of operations equivalent to individual patches. + */ + private fun JSONArray.patchMergeMap(): MutableMap, JSONObject> { + return (0 until this.length()) + .map { this.optJSONObject(it) } + .associateBy { it.optString("op") to it.optString("path") } + .toMutableMap() + } } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt b/core/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt index bc4dfb4871..68f55f53f3 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/dao/ResourceDao.kt @@ -39,92 +39,89 @@ import org.hl7.fhir.r4.model.ResourceType @Dao internal abstract class ResourceDao { - // this is ugly but there is no way to inject these right now in Room as it is the one creating - // the dao - lateinit var iParser: IParser - - @Transaction - open fun update(resource: Resource) { - updateResource(resource.id, - resource.resourceType, - iParser.encodeResourceToString(resource) - ) - val entity = ResourceEntity( - id = 0, - resourceType = resource.resourceType, - resourceId = resource.id, - serializedResource = iParser.encodeResourceToString(resource) - ) - val index = ResourceIndexer.index(resource) - updateIndicesForResource(index, entity) - } + // this is ugly but there is no way to inject these right now in Room as it is the one creating + // the dao + lateinit var iParser: IParser - @Transaction - open fun insert(resource: Resource) { - insertResource(resource) - } + @Transaction + open fun update(resource: Resource) { + updateResource(resource.id, resource.resourceType, iParser.encodeResourceToString(resource)) + val entity = + ResourceEntity( + id = 0, + resourceType = resource.resourceType, + resourceId = resource.id, + serializedResource = iParser.encodeResourceToString(resource) + ) + val index = ResourceIndexer.index(resource) + updateIndicesForResource(index, entity) + } - @Transaction - open fun insertAll(resources: List) { - resources.forEach { resource -> - insertResource(resource) - } - } + @Transaction + open fun insert(resource: Resource) { + insertResource(resource) + } + + @Transaction + open fun insertAll(resources: List) { + resources.forEach { resource -> insertResource(resource) } + } - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertResource(resource: ResourceEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertResource(resource: ResourceEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertStringIndex(stringIndexEntity: StringIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertStringIndex(stringIndexEntity: StringIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertReferenceIndex(referenceIndexEntity: ReferenceIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertReferenceIndex(referenceIndexEntity: ReferenceIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertCodeIndex(tokenIndexEntity: TokenIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertCodeIndex(tokenIndexEntity: TokenIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertQuantityIndex(quantityIndexEntity: QuantityIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertQuantityIndex(quantityIndexEntity: QuantityIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertUriIndex(uriIndexEntity: UriIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertUriIndex(uriIndexEntity: UriIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertDateIndex(dateIndexEntity: DateIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertDateIndex(dateIndexEntity: DateIndexEntity) - @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract fun insertNumberIndex(numberIndexEntity: NumberIndexEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertNumberIndex(numberIndexEntity: NumberIndexEntity) - @Query(""" + @Query( + """ UPDATE ResourceEntity SET serializedResource = :serializedResource WHERE resourceId = :resourceId AND resourceType = :resourceType - """) - abstract fun updateResource( - resourceId: String, - resourceType: ResourceType, - serializedResource: String - ) - - @Query(""" + """ + ) + abstract fun updateResource( + resourceId: String, + resourceType: ResourceType, + serializedResource: String + ) + + @Query( + """ DELETE FROM ResourceEntity - WHERE resourceId = :resourceId AND resourceType = :resourceType""") - abstract fun deleteResource( - resourceId: String, - resourceType: ResourceType - ): Int + WHERE resourceId = :resourceId AND resourceType = :resourceType""" + ) + abstract fun deleteResource(resourceId: String, resourceType: ResourceType): Int - @Query(""" + @Query( + """ SELECT serializedResource FROM ResourceEntity - WHERE resourceId = :resourceId AND resourceType = :resourceType""") - abstract fun getResource( - resourceId: String, - resourceType: ResourceType - ): String? + WHERE resourceId = :resourceId AND resourceType = :resourceType""" + ) + abstract fun getResource(resourceId: String, resourceType: ResourceType): String? - @Query(""" + @Query( + """ SELECT ResourceEntity.serializedResource FROM ResourceEntity JOIN ReferenceIndexEntity @@ -132,14 +129,16 @@ internal abstract class ResourceDao { AND ResourceEntity.resourceId = ReferenceIndexEntity.resourceId WHERE ReferenceIndexEntity.resourceType = :resourceType AND ReferenceIndexEntity.index_path = :indexPath - AND ReferenceIndexEntity.index_value = :indexValue""") - abstract fun getResourceByReferenceIndex( - resourceType: String, - indexPath: String, - indexValue: String - ): List - - @Query(""" + AND ReferenceIndexEntity.index_value = :indexValue""" + ) + abstract fun getResourceByReferenceIndex( + resourceType: String, + indexPath: String, + indexValue: String + ): List + + @Query( + """ SELECT ResourceEntity.serializedResource FROM ResourceEntity JOIN StringIndexEntity @@ -147,14 +146,16 @@ internal abstract class ResourceDao { AND ResourceEntity.resourceId = StringIndexEntity.resourceId WHERE StringIndexEntity.resourceType = :resourceType AND StringIndexEntity.index_path = :indexPath - AND StringIndexEntity.index_value = :indexValue""") - abstract fun getResourceByStringIndex( - resourceType: String, - indexPath: String, - indexValue: String - ): List - - @Query(""" + AND StringIndexEntity.index_value = :indexValue""" + ) + abstract fun getResourceByStringIndex( + resourceType: String, + indexPath: String, + indexValue: String + ): List + + @Query( + """ SELECT ResourceEntity.serializedResource FROM ResourceEntity JOIN TokenIndexEntity @@ -163,89 +164,105 @@ internal abstract class ResourceDao { WHERE TokenIndexEntity.resourceType = :resourceType AND TokenIndexEntity.index_path = :indexPath AND TokenIndexEntity.index_system = :indexSystem - AND TokenIndexEntity.index_value = :indexValue""") - abstract fun getResourceByCodeIndex( - resourceType: String, - indexPath: String, - indexSystem: String, - indexValue: String - ): List - - @RawQuery - abstract fun getResources(query: SupportSQLiteQuery): List - - private fun insertResource(resource: Resource) { - val entity = ResourceEntity( - id = 0, - resourceType = resource.resourceType, - resourceId = resource.id, - serializedResource = iParser.encodeResourceToString(resource) + AND TokenIndexEntity.index_value = :indexValue""" + ) + abstract fun getResourceByCodeIndex( + resourceType: String, + indexPath: String, + indexSystem: String, + indexValue: String + ): List + + @RawQuery abstract fun getResources(query: SupportSQLiteQuery): List + + private fun insertResource(resource: Resource) { + val entity = + ResourceEntity( + id = 0, + resourceType = resource.resourceType, + resourceId = resource.id, + serializedResource = iParser.encodeResourceToString(resource) + ) + insertResource(entity) + val index = ResourceIndexer.index(resource) + updateIndicesForResource(index, entity) + } + + private fun updateIndicesForResource(index: ResourceIndices, resource: ResourceEntity) { + // TODO Move StringIndices to persistable types + // https://github.com/jingtang10/fhir-engine/issues/31 + // we can either use room-autovalue integration or go w/ embedded data classes. + // we may also want to merge them: + // https://github.com/jingtang10/fhir-engine/issues/33 + index.stringIndices.forEach { + insertStringIndex( + StringIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId ) - insertResource(entity) - val index = ResourceIndexer.index(resource) - updateIndicesForResource(index, entity) + ) } - - private fun updateIndicesForResource(index: ResourceIndices, resource: ResourceEntity) { - // TODO Move StringIndices to persistable types - // https://github.com/jingtang10/fhir-engine/issues/31 - // we can either use room-autovalue integration or go w/ embedded data classes. - // we may also want to merge them: - // https://github.com/jingtang10/fhir-engine/issues/33 - index.stringIndices.forEach { - insertStringIndex( - StringIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId - ) - ) - } - index.referenceIndices.forEach { - insertReferenceIndex( - ReferenceIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId - ) - ) - } - index.tokenIndices.forEach { - insertCodeIndex(TokenIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId)) - } - index.quantityIndices.forEach { - insertQuantityIndex(QuantityIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId)) - } - index.uriIndices.forEach { - insertUriIndex(UriIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId)) - } - index.dateIndices.forEach { - insertDateIndex(DateIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId)) - } - index.numberIndices.forEach { - insertNumberIndex(NumberIndexEntity( - id = 0, - resourceType = resource.resourceType, - index = it, - resourceId = resource.resourceId)) - } + index.referenceIndices.forEach { + insertReferenceIndex( + ReferenceIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) + } + index.tokenIndices.forEach { + insertCodeIndex( + TokenIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) + } + index.quantityIndices.forEach { + insertQuantityIndex( + QuantityIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) + } + index.uriIndices.forEach { + insertUriIndex( + UriIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) + } + index.dateIndices.forEach { + insertDateIndex( + DateIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) + } + index.numberIndices.forEach { + insertNumberIndex( + NumberIndexEntity( + id = 0, + resourceType = resource.resourceType, + index = it, + resourceId = resource.resourceId + ) + ) } + } } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/dao/SyncedResourceDao.kt b/core/src/main/java/com/google/android/fhir/db/impl/dao/SyncedResourceDao.kt index f9b629dc91..df00833477 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/dao/SyncedResourceDao.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/dao/SyncedResourceDao.kt @@ -26,15 +26,15 @@ import org.hl7.fhir.r4.model.ResourceType @Dao interface SyncedResourceDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: SyncedResourceEntity) + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(entity: SyncedResourceEntity) - /** - * We will always have 1 entry for each [ResourceType] as it's the primary key, so - * we can limit the result to 1. If there is no entry for that [ResourceType] then `null` - * will be returned. - */ - @Query("""SELECT lastUpdate FROM SyncedResourceEntity - WHERE resourceType = :resourceType LIMIT 1""") - suspend fun getLastUpdate(resourceType: ResourceType): String? + /** + * We will always have 1 entry for each [ResourceType] as it's the primary key, so we can limit + * the result to 1. If there is no entry for that [ResourceType] then `null` will be returned. + */ + @Query( + """SELECT lastUpdate FROM SyncedResourceEntity + WHERE resourceType = :resourceType LIMIT 1""" + ) + suspend fun getLastUpdate(resourceType: ResourceType): String? } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/DateIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/DateIndexEntity.kt index 5a01685c5f..5467c3f14e 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/DateIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/DateIndexEntity.kt @@ -25,30 +25,27 @@ import com.google.android.fhir.index.entities.DateIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_tsHigh", "index_tsLow"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ]) + indices = + [ + Index(value = ["resourceType", "index_name", "index_tsHigh", "index_tsLow"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] +) internal data class DateIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - @Embedded(prefix = "index_") - val index: DateIndex, - val resourceId: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + @Embedded(prefix = "index_") val index: DateIndex, + val resourceId: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/LocalChangeEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/LocalChangeEntity.kt index c20d2878a6..b7eca001b6 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/LocalChangeEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/LocalChangeEntity.kt @@ -21,12 +21,12 @@ import androidx.room.Index import androidx.room.PrimaryKey /** - * When a local change to a resource happens, the lastUpdated timestamp in - * [ResourceEntity] is updated and the diff itself is inserted in this table. - * The value of the diff depends upon the type of change and can be: + * When a local change to a resource happens, the lastUpdated timestamp in [ResourceEntity] is + * updated and the diff itself is inserted in this table. The value of the diff depends upon the + * type of change and can be: * * DELETE: The empty string, "". - * * INSERT: The full resource in JSON form, e.g. - * { + * * INSERT: The full resource in JSON form, e.g. { + * ``` * "resourceType": "Patient", * "id": "animal", * "name": [ @@ -38,40 +38,34 @@ import androidx.room.PrimaryKey * } * ], * ... + * ``` * } - * * UPDATE: A RFC 6902 JSON patch. e.g. a patch that changes the given name of a patient: - * [ + * * UPDATE: A RFC 6902 JSON patch. e.g. a patch that changes the given name of a patient: [ + * ``` * { * "op": "replace", * "path": "/name/0/given/0", * "value": "Binny" * } - * ] - * For resource that is fully synced with server this table should not have any rows. + * ``` + * ] For resource that is fully synced with server this table should not have any rows. */ -@Entity( - indices = [ - Index( - value = ["resourceType", "resourceId"] - ) - ] -) +@Entity(indices = [Index(value = ["resourceType", "resourceId"])]) data class LocalChangeEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: String, - val resourceId: String, - val timestamp: String = "", - val type: Type, - val payload: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: String, + val resourceId: String, + val timestamp: String = "", + val type: Type, + val payload: String ) { - enum class Type(val value: Int) { - INSERT(1), // create a new resource. payload is the entire resource json. - UPDATE(2), // patch. payload is the json patch. - DELETE(3); // delete. payload is empty string. + enum class Type(val value: Int) { + INSERT(1), // create a new resource. payload is the entire resource json. + UPDATE(2), // patch. payload is the json patch. + DELETE(3); // delete. payload is empty string. - companion object { - fun from(input: Int): Type = values().first { it.value == input } - } + companion object { + fun from(input: Int): Type = values().first { it.value == input } } + } } diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/NumberIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/NumberIndexEntity.kt index 8873b84e5f..7430533f19 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/NumberIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/NumberIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.NumberIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_value"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_value"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class NumberIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - @Embedded(prefix = "index_") - val index: NumberIndex, - val resourceId: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + @Embedded(prefix = "index_") val index: NumberIndex, + val resourceId: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/QuantityIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/QuantityIndexEntity.kt index 89cbaaa2ff..643bebf0c2 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/QuantityIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/QuantityIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.QuantityIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_value"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_value"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class QuantityIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - val resourceId: String, - @Embedded(prefix = "index_") - val index: QuantityIndex + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + val resourceId: String, + @Embedded(prefix = "index_") val index: QuantityIndex ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/ReferenceIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/ReferenceIndexEntity.kt index 8b994b7550..21f9c5e949 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/ReferenceIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/ReferenceIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.ReferenceIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_value"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_value"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class ReferenceIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - @Embedded(prefix = "index_") - val index: ReferenceIndex, - val resourceId: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + @Embedded(prefix = "index_") val index: ReferenceIndex, + val resourceId: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/ResourceEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/ResourceEntity.kt index 8b3645e658..13254283e5 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/ResourceEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/ResourceEntity.kt @@ -21,18 +21,10 @@ import androidx.room.Index import androidx.room.PrimaryKey import org.hl7.fhir.r4.model.ResourceType -@Entity( - indices = [ - Index( - value = ["resourceType", "resourceId"], - unique = true - ) - ] -) +@Entity(indices = [Index(value = ["resourceType", "resourceId"], unique = true)]) internal data class ResourceEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - val resourceId: String, - val serializedResource: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + val resourceId: String, + val serializedResource: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/StringIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/StringIndexEntity.kt index a105d25461..d927e58c3e 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/StringIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/StringIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.StringIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_value"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_value"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class StringIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - @Embedded(prefix = "index_") - val index: StringIndex, - val resourceId: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + @Embedded(prefix = "index_") val index: StringIndex, + val resourceId: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/SyncedResourceEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/SyncedResourceEntity.kt index accb18f20d..3f250c242d 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/SyncedResourceEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/SyncedResourceEntity.kt @@ -26,13 +26,8 @@ import org.hl7.fhir.r4.model.ResourceType */ @Entity data class SyncedResourceEntity( - /** - * Resource synced - */ - @PrimaryKey - val resourceType: ResourceType, - /** - * The highest `_lastUpdate` value of the resources synced of a specific type - */ - val lastUpdate: String + /** Resource synced */ + @PrimaryKey val resourceType: ResourceType, + /** The highest `_lastUpdate` value of the resources synced of a specific type */ + val lastUpdate: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/TokenIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/TokenIndexEntity.kt index f18125a9d9..431adca477 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/TokenIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/TokenIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.TokenIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_system", "index_value"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_system", "index_value"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class TokenIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - @Embedded(prefix = "index_") - val index: TokenIndex, - val resourceId: String + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + @Embedded(prefix = "index_") val index: TokenIndex, + val resourceId: String ) diff --git a/core/src/main/java/com/google/android/fhir/db/impl/entities/UriIndexEntity.kt b/core/src/main/java/com/google/android/fhir/db/impl/entities/UriIndexEntity.kt index 7cf234b3d6..873e434674 100644 --- a/core/src/main/java/com/google/android/fhir/db/impl/entities/UriIndexEntity.kt +++ b/core/src/main/java/com/google/android/fhir/db/impl/entities/UriIndexEntity.kt @@ -25,31 +25,27 @@ import com.google.android.fhir.index.entities.UriIndex import org.hl7.fhir.r4.model.ResourceType @Entity( - indices = [ - Index( - value = ["resourceType", "index_name", "index_uri"] - ), - Index( - // keep this index for faster foreign lookup - value = ["resourceId", "resourceType"] - ) - ], - foreignKeys = [ - ForeignKey( - entity = ResourceEntity::class, - parentColumns = ["resourceId", "resourceType"], - childColumns = ["resourceId", "resourceType"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.NO_ACTION, - deferred = true - ) - ] + indices = + [ + Index(value = ["resourceType", "index_name", "index_uri"]), + Index( + // keep this index for faster foreign lookup + value = ["resourceId", "resourceType"] + )], + foreignKeys = + [ + ForeignKey( + entity = ResourceEntity::class, + parentColumns = ["resourceId", "resourceType"], + childColumns = ["resourceId", "resourceType"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.NO_ACTION, + deferred = true + )] ) internal data class UriIndexEntity( - @PrimaryKey(autoGenerate = true) - val id: Long, - val resourceType: ResourceType, - val resourceId: String, - @Embedded(prefix = "index_") - val index: UriIndex + @PrimaryKey(autoGenerate = true) val id: Long, + val resourceType: ResourceType, + val resourceId: String, + @Embedded(prefix = "index_") val index: UriIndex ) diff --git a/core/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt b/core/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt index e4e7a3b308..85e17b4e1f 100644 --- a/core/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt +++ b/core/src/main/java/com/google/android/fhir/impl/FhirEngineImpl.kt @@ -44,126 +44,115 @@ import org.opencds.cqf.cql.execution.EvaluationResult import org.opencds.cqf.cql.execution.LibraryLoader import org.opencds.cqf.cql.terminology.TerminologyProvider -/** Implementation of [FhirEngine]. */ -class FhirEngineImpl constructor( - private val database: Database, - private val search: Search, - libraryLoader: LibraryLoader, - dataProviderMap: Map, - terminologyProvider: TerminologyProvider, - private var periodicSyncConfiguration: PeriodicSyncConfiguration?, - private val dataSource: FhirDataSource, - private val context: Context +/** Implementation of [FhirEngine]. */ +class FhirEngineImpl +constructor( + private val database: Database, + private val search: Search, + libraryLoader: LibraryLoader, + dataProviderMap: Map, + terminologyProvider: TerminologyProvider, + private var periodicSyncConfiguration: PeriodicSyncConfiguration?, + private val dataSource: FhirDataSource, + private val context: Context ) : FhirEngine { - init { - periodicSyncConfiguration?.let { config -> - triggerInitialDownload(config) - } - } + init { + periodicSyncConfiguration?.let { config -> triggerInitialDownload(config) } + } - private val cqlEngine: CqlEngine = CqlEngine( - libraryLoader, - dataProviderMap, - terminologyProvider, - EnumSet.noneOf(CqlEngine.Options::class.java) + private val cqlEngine: CqlEngine = + CqlEngine( + libraryLoader, + dataProviderMap, + terminologyProvider, + EnumSet.noneOf(CqlEngine.Options::class.java) ) - override fun save(resource: R) { - database.insert(resource) - } - - override fun saveAll(resources: List) { - database.insertAll(resources) - } - - override fun update(resource: R) { - database.update(resource) - } - - @Throws(ResourceNotFoundException::class) - override fun load(clazz: Class, id: String): R { - return try { - database.select(clazz, id) - } catch (e: ResourceNotFoundInDbException) { - throw ResourceNotFoundException(getResourceType(clazz).name, id, e) - } - } - - override fun remove(clazz: Class, id: String) { - database.delete(clazz, id) - } - - override fun evaluateCql( - libraryVersionId: String, - context: String, - expression: String - ): EvaluationResult { - val contextMap: MutableMap = HashMap() - val contextSplit = context.split("/").toTypedArray() - contextMap[contextSplit[0]] = contextSplit[1] - val versionedIdentifier = VersionedIdentifier().withId(libraryVersionId) - val expressions: MutableSet = HashSet() - expressions.add(expression) - val map: MutableMap> = HashMap() - map[versionedIdentifier] = expressions - return cqlEngine.evaluate(contextMap, null, map) - } - - override fun search(): Search { - return search - } + override fun save(resource: R) { + database.insert(resource) + } - override suspend fun sync(syncConfiguration: SyncConfiguration): Result { - return FhirSynchronizer(syncConfiguration, dataSource, database).sync() - } - - override suspend fun periodicSync(): Result { - val syncConfig = periodicSyncConfiguration ?: throw java.lang.UnsupportedOperationException( - "Periodic sync configuration was not set" - ) - val syncResult = FhirSynchronizer( - syncConfig.syncConfiguration, - dataSource, - database - ).sync() - setupNextDownload(syncConfig) - return syncResult - } + override fun saveAll(resources: List) { + database.insertAll(resources) + } - override fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration) { - periodicSyncConfiguration = syncConfig - setupNextDownload(syncConfig) - } - - private fun setupNextDownload(syncConfig: PeriodicSyncConfiguration) { - setupDownload(syncConfig = syncConfig, withInitialDelay = true) - } - - private fun triggerInitialDownload(syncConfig: PeriodicSyncConfiguration) { - setupDownload(syncConfig = syncConfig, withInitialDelay = false) - } + override fun update(resource: R) { + database.update(resource) + } - private fun setupDownload(syncConfig: PeriodicSyncConfiguration, withInitialDelay: Boolean) { - val workerClass = syncConfig.periodicSyncWorker - val downloadRequest = if (withInitialDelay) { - OneTimeWorkRequest.Builder(workerClass) - .setConstraints(syncConfig.syncConstraints) - .setInitialDelay( - syncConfig.repeat.interval, - syncConfig.repeat.timeUnit - ) - .build() - } else { - OneTimeWorkRequest.Builder(workerClass) - .setConstraints(syncConfig.syncConstraints) - .build() - } - - WorkManager.getInstance(context).enqueueUniqueWork( - SyncWorkType.DOWNLOAD.workerName, - ExistingWorkPolicy.KEEP, - downloadRequest - ) + @Throws(ResourceNotFoundException::class) + override fun load(clazz: Class, id: String): R { + return try { + database.select(clazz, id) + } catch (e: ResourceNotFoundInDbException) { + throw ResourceNotFoundException(getResourceType(clazz).name, id, e) } + } + + override fun remove(clazz: Class, id: String) { + database.delete(clazz, id) + } + + override fun evaluateCql( + libraryVersionId: String, + context: String, + expression: String + ): EvaluationResult { + val contextMap: MutableMap = HashMap() + val contextSplit = context.split("/").toTypedArray() + contextMap[contextSplit[0]] = contextSplit[1] + val versionedIdentifier = VersionedIdentifier().withId(libraryVersionId) + val expressions: MutableSet = HashSet() + expressions.add(expression) + val map: MutableMap> = HashMap() + map[versionedIdentifier] = expressions + return cqlEngine.evaluate(contextMap, null, map) + } + + override fun search(): Search { + return search + } + + override suspend fun sync(syncConfiguration: SyncConfiguration): Result { + return FhirSynchronizer(syncConfiguration, dataSource, database).sync() + } + + override suspend fun periodicSync(): Result { + val syncConfig = + periodicSyncConfiguration + ?: throw java.lang.UnsupportedOperationException("Periodic sync configuration was not set") + val syncResult = FhirSynchronizer(syncConfig.syncConfiguration, dataSource, database).sync() + setupNextDownload(syncConfig) + return syncResult + } + + override fun updatePeriodicSyncConfiguration(syncConfig: PeriodicSyncConfiguration) { + periodicSyncConfiguration = syncConfig + setupNextDownload(syncConfig) + } + + private fun setupNextDownload(syncConfig: PeriodicSyncConfiguration) { + setupDownload(syncConfig = syncConfig, withInitialDelay = true) + } + + private fun triggerInitialDownload(syncConfig: PeriodicSyncConfiguration) { + setupDownload(syncConfig = syncConfig, withInitialDelay = false) + } + + private fun setupDownload(syncConfig: PeriodicSyncConfiguration, withInitialDelay: Boolean) { + val workerClass = syncConfig.periodicSyncWorker + val downloadRequest = + if (withInitialDelay) { + OneTimeWorkRequest.Builder(workerClass) + .setConstraints(syncConfig.syncConstraints) + .setInitialDelay(syncConfig.repeat.interval, syncConfig.repeat.timeUnit) + .build() + } else { + OneTimeWorkRequest.Builder(workerClass).setConstraints(syncConfig.syncConstraints).build() + } + + WorkManager.getInstance(context) + .enqueueUniqueWork(SyncWorkType.DOWNLOAD.workerName, ExistingWorkPolicy.KEEP, downloadRequest) + } } diff --git a/core/src/main/java/com/google/android/fhir/index/ResourceIndexer.kt b/core/src/main/java/com/google/android/fhir/index/ResourceIndexer.kt index 502dc42552..4f477c52dd 100644 --- a/core/src/main/java/com/google/android/fhir/index/ResourceIndexer.kt +++ b/core/src/main/java/com/google/android/fhir/index/ResourceIndexer.kt @@ -41,338 +41,349 @@ import org.hl7.fhir.r4.model.StringType import org.hl7.fhir.r4.model.UriType internal object ResourceIndexer { - fun index(resource: R) = extractIndexValues(resource) + fun index(resource: R) = extractIndexValues(resource) - /** Extracts the values to be indexed for `resource`. */ - private fun extractIndexValues(resource: R): ResourceIndices { - val indexBuilder = ResourceIndices.Builder(resource.resourceType, resource.id) - resource.javaClass.fields.asSequence().mapNotNull { - it.getAnnotation(SearchParamDefinition::class.java) - }.filter { - it.path.hasDotNotationOnly() - }.forEach { searchParamDefinition -> - when (searchParamDefinition.type) { - SEARCH_PARAM_DEFINITION_TYPE_STRING -> { - resource.valuesForPath(searchParamDefinition).stringValues().forEach { value -> - indexBuilder.addStringIndex( - StringIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - value = value - )) - } - } - SEARCH_PARAM_DEFINITION_TYPE_REFERENCE -> { - resource.valuesForPath(searchParamDefinition) - .referenceValues() - .forEach { reference -> - if (reference.reference?.isNotEmpty() == true) { - indexBuilder.addReferenceIndex( - ReferenceIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - value = reference.reference - )) - } - } - } - SEARCH_PARAM_DEFINITION_TYPE_CODE -> { - resource.valuesForPath(searchParamDefinition).codeValues().forEach { code -> - val system = code.system - val value = code.code - if (system?.isNotEmpty() == true && value?.isNotEmpty() == true) { - indexBuilder.addTokenIndex( - TokenIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - system = system, - value = value - )) - } - } - } - SEARCH_PARAM_DEFINITION_TYPE_QUANTITY -> { - resource.valuesForPath(searchParamDefinition) - .quantityValues() - .forEach { quantity -> - - val system: String - val unit: String - val value: BigDecimal - - if (quantity is Quantity) { - system = quantity.system - unit = quantity.unit - value = quantity.value - } else if (quantity is Money) { - system = FHIR_CURRENCY_SYSTEM - unit = quantity.currency - value = quantity.value - } else { - throw IllegalArgumentException( - "$quantity is of unknown type ${quantity.javaClass.simpleName}") - } + /** Extracts the values to be indexed for `resource`. */ + private fun extractIndexValues(resource: R): ResourceIndices { + val indexBuilder = ResourceIndices.Builder(resource.resourceType, resource.id) + resource + .javaClass + .fields + .asSequence() + .mapNotNull { it.getAnnotation(SearchParamDefinition::class.java) } + .filter { it.path.hasDotNotationOnly() } + .forEach { searchParamDefinition -> + when (searchParamDefinition.type) { + SEARCH_PARAM_DEFINITION_TYPE_STRING -> { + resource.valuesForPath(searchParamDefinition).stringValues().forEach { value -> + indexBuilder.addStringIndex( + StringIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + value = value + ) + ) + } + } + SEARCH_PARAM_DEFINITION_TYPE_REFERENCE -> { + resource.valuesForPath(searchParamDefinition).referenceValues().forEach { reference -> + if (reference.reference?.isNotEmpty() == true) { + indexBuilder.addReferenceIndex( + ReferenceIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + value = reference.reference + ) + ) + } + } + } + SEARCH_PARAM_DEFINITION_TYPE_CODE -> { + resource.valuesForPath(searchParamDefinition).codeValues().forEach { code -> + val system = code.system + val value = code.code + if (system?.isNotEmpty() == true && value?.isNotEmpty() == true) { + indexBuilder.addTokenIndex( + TokenIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + system = system, + value = value + ) + ) + } + } + } + SEARCH_PARAM_DEFINITION_TYPE_QUANTITY -> { + resource.valuesForPath(searchParamDefinition).quantityValues().forEach { quantity -> + val system: String + val unit: String + val value: BigDecimal - indexBuilder.addQuantityIndex( - QuantityIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - system = system, - unit = unit, - value = value - )) - } - } - SEARCH_PARAM_DEFINITION_TYPE_URI -> { - resource.valuesForPath(searchParamDefinition) - .uriValues() - .forEach { uri -> - indexBuilder.addUriIndex( - UriIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - uri = uri - )) - } - } - SEARCH_PARAM_DEFINITION_TYPE_DATE -> { - resource.valuesForPath(searchParamDefinition).dateValues().forEach { date -> - indexBuilder.addDateIndex( - DateIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - tsHigh = date.value.time, - tsLow = date.value.time, - temporalPrecision = date.precision)) - } - } - SEARCH_PARAM_DEFINITION_TYPE_NUMBER -> { - resource.valuesForPath(searchParamDefinition).numberValues().forEach { number -> - indexBuilder.addNumberIndex( - NumberIndex( - name = searchParamDefinition.name, - path = searchParamDefinition.path, - value = number)) - } - } + if (quantity is Quantity) { + system = quantity.system + unit = quantity.unit + value = quantity.value + } else if (quantity is Money) { + system = FHIR_CURRENCY_SYSTEM + unit = quantity.currency + value = quantity.value + } else { + throw IllegalArgumentException( + "$quantity is of unknown type ${quantity.javaClass.simpleName}" + ) + } - // TODO: Implement token, composite and special search parameter types. + indexBuilder.addQuantityIndex( + QuantityIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + system = system, + unit = unit, + value = value + ) + ) } - } - // For all resources, - // add 'last updated' timestamp to date index - if (resource.meta.hasLastUpdated()) { - val lastUpdatedElement = resource.meta.lastUpdatedElement - indexBuilder.addDateIndex( + } + SEARCH_PARAM_DEFINITION_TYPE_URI -> { + resource.valuesForPath(searchParamDefinition).uriValues().forEach { uri -> + indexBuilder.addUriIndex( + UriIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + uri = uri + ) + ) + } + } + SEARCH_PARAM_DEFINITION_TYPE_DATE -> { + resource.valuesForPath(searchParamDefinition).dateValues().forEach { date -> + indexBuilder.addDateIndex( DateIndex( - name = "lastUpdated", - path = arrayOf(resource.fhirType(), "meta", "lastUpdated") - .joinToString(separator = "."), - tsHigh = lastUpdatedElement.value.time, - tsLow = lastUpdatedElement.value.time, - temporalPrecision = lastUpdatedElement.precision - )) - } - return indexBuilder.build() - } - - /** - * Returns the representative string values for the list of `objects`. - * - * If an object in the list is a Java [String], the returned list will contain the value of - * the Java [String]. If an object in the list is a FHIR [StringType], the returned - * list will contain the value of the FHIR [StringType]. If an object in the list matches a - * server defined search type (HumanName, Address, etc), the returned list will contain the - * string value representative of the type. - */ - private fun Sequence.stringValues(): Sequence { - return mapNotNull { - when (it) { - is String -> { - it - } - is StringType -> { - it.value - } - else -> { - // TODO: Implement the server defined search parameters. According to - // https://www.hl7.org/fhir/searchparameter-registry.html, name, device name, - // and address are defined by the server - // (the FHIR Engine library in this case). - null - } + name = searchParamDefinition.name, + path = searchParamDefinition.path, + tsHigh = date.value.time, + tsLow = date.value.time, + temporalPrecision = date.precision + ) + ) } - } - } + } + SEARCH_PARAM_DEFINITION_TYPE_NUMBER -> { + resource.valuesForPath(searchParamDefinition).numberValues().forEach { number -> + indexBuilder.addNumberIndex( + NumberIndex( + name = searchParamDefinition.name, + path = searchParamDefinition.path, + value = number + ) + ) + } + } - /** Returns the reference values for the list of `objects`. */ - private fun Sequence.referenceValues(): Sequence { - return filterIsInstance(Reference::class.java) + // TODO: Implement token, composite and special search parameter types. + } + } + // For all resources, + // add 'last updated' timestamp to date index + if (resource.meta.hasLastUpdated()) { + val lastUpdatedElement = resource.meta.lastUpdatedElement + indexBuilder.addDateIndex( + DateIndex( + name = "lastUpdated", + path = arrayOf(resource.fhirType(), "meta", "lastUpdated").joinToString(separator = "."), + tsHigh = lastUpdatedElement.value.time, + tsLow = lastUpdatedElement.value.time, + temporalPrecision = lastUpdatedElement.precision + ) + ) } + return indexBuilder.build() + } - /** Returns the code values for the list of `objects`. */ - private fun Sequence.codeValues(): Sequence { - return flatMap { - if (it is CodeableConcept) { - it.coding.asSequence() - } else { - emptySequence() - } + /** + * Returns the representative string values for the list of `objects`. + * + * If an object in the list is a Java [String], the returned list will contain the value of the + * Java [String]. If an object in the list is a FHIR [StringType], the returned list will contain + * the value of the FHIR [StringType]. If an object in the list matches a server defined search + * type (HumanName, Address, etc), the returned list will contain the string value representative + * of the type. + */ + private fun Sequence.stringValues(): Sequence { + return mapNotNull { + when (it) { + is String -> { + it + } + is StringType -> { + it.value } + else -> { + // TODO: Implement the server defined search parameters. According to + // https://www.hl7.org/fhir/searchparameter-registry.html, name, device name, + // and address are defined by the server + // (the FHIR Engine library in this case). + null + } + } } + } - /** Returns the quantity values for the list of `objects`. */ - private fun Sequence.quantityValues(): Sequence { - return flatMap { - when (it) { - is Money -> sequenceOf(it).filter { it.hasCurrency() } - is Quantity -> sequenceOf(it).filter { it.hasSystem() && it.hasCode() } - is Range -> sequenceOf(it.low, it.high).filter { it.hasSystem() && it.hasCode() } - is Ratio -> sequenceOf(it.numerator, it.denominator) - .filter { it.hasSystem() && it.hasCode() } - // TODO: Find other FHIR datatypes types the "quantity" type maps to. - // See: http://hl7.org/fhir/datatypes.html#quantity - // TODO: Add tests for Range and Ratio types - else -> emptySequence() - } - } + /** Returns the reference values for the list of `objects`. */ + private fun Sequence.referenceValues(): Sequence { + return filterIsInstance(Reference::class.java) + } + + /** Returns the code values for the list of `objects`. */ + private fun Sequence.codeValues(): Sequence { + return flatMap { + if (it is CodeableConcept) { + it.coding.asSequence() + } else { + emptySequence() + } } + } - /** Returns the uri values for the list of `objects`. */ - private fun Sequence.uriValues(): Sequence { - return flatMap { - when (it) { - is UriType -> sequenceOf(it.value) - is String -> sequenceOf(it) - else -> emptySequence() - } - } + /** Returns the quantity values for the list of `objects`. */ + private fun Sequence.quantityValues(): Sequence { + return flatMap { + when (it) { + is Money -> sequenceOf(it).filter { it.hasCurrency() } + is Quantity -> sequenceOf(it).filter { it.hasSystem() && it.hasCode() } + is Range -> sequenceOf(it.low, it.high).filter { it.hasSystem() && it.hasCode() } + is Ratio -> + sequenceOf(it.numerator, it.denominator).filter { it.hasSystem() && it.hasCode() } + // TODO: Find other FHIR datatypes types the "quantity" type maps to. + // See: http://hl7.org/fhir/datatypes.html#quantity + // TODO: Add tests for Range and Ratio types + else -> emptySequence() + } } + } - /** Returns the Date values for a list of `objects`. */ - private fun Sequence.dateValues(): Sequence { - return flatMap { - /** BaseDateTimeType wraps around [java.util.Date] which is what we use to extract the - * timestamp. Some implementations return the timestamp in local (device) timezone. - * Additionally, time zones are likely to be missing from health data. Date indexing - * is a work in progress. - */ - if (it is BaseDateTimeType) { - sequenceOf(it) - } else { - emptySequence() - } - }.filter { it.value != null } + /** Returns the uri values for the list of `objects`. */ + private fun Sequence.uriValues(): Sequence { + return flatMap { + when (it) { + is UriType -> sequenceOf(it.value) + is String -> sequenceOf(it) + else -> emptySequence() + } } + } - /** Returns the number values for a list of `objects`. */ - private fun Sequence.numberValues(): Sequence { - return flatMap { - when { - it is Integer -> sequenceOf(it.toInt().toBigDecimal()) - it is BigDecimal -> sequenceOf(it) - else -> emptySequence() - } - }.filterNotNull() + /** Returns the Date values for a list of `objects`. */ + private fun Sequence.dateValues(): Sequence { + return flatMap { + /** + * BaseDateTimeType wraps around [java.util.Date] which is what we use to extract the + * timestamp. Some implementations return the timestamp in local (device) timezone. + * Additionally, time zones are likely to be missing from health data. Date indexing is a work + * in progress. + */ + if (it is BaseDateTimeType) { + sequenceOf(it) + } else { + emptySequence() + } } + .filter { it.value != null } + } - /** Returns the list of values corresponding to the `path` in the `resource`. */ - private fun Resource.valuesForPath(definition: SearchParamDefinition): Sequence { - val paths = definition.path.split(SEPARATOR_REGEX) - if (paths.size <= 1) { - return emptySequence() - } - return paths.asSequence().drop(1).fold(sequenceOf(this)) { acc, next -> - getFieldValues(acc, next, definition.type) + /** Returns the number values for a list of `objects`. */ + private fun Sequence.numberValues(): Sequence { + return flatMap { + when { + it is Integer -> sequenceOf(it.toInt().toBigDecimal()) + it is BigDecimal -> sequenceOf(it) + else -> emptySequence() } - } + } + .filterNotNull() + } - /** - * Returns the list of field values for `fieldName` in each of the `objects`. - * - * If the field is a [Collection], it will be expanded and each element of the [Collection] - * will be added to the returned value. - */ - private fun getFieldValues( - objects: Sequence, - fieldName: String, - type: String - ): Sequence { - return objects.asSequence().flatMap { - val value = try { - /* TODO - * Upstream HAPI FHIR returns FHIR date* types from getxxDate methods as - * java.util.Date. For HAPI FHIR BaseDateTimeTypes, which support TimeZones - * we need to invoke() getxxDateElement. Hence we need to pass in search parameter - * type to getGetterName below. - */ - it.javaClass.getMethod(getGetterName(fieldName, type)).invoke(it) - } catch (error: Throwable) { - Log.w(TAG, error) - null - } - if (value is Collection<*>) { - value.asSequence() - } else { - sequenceOf(value) - } - }.filterNotNull() + /** Returns the list of values corresponding to the `path` in the `resource`. */ + private fun Resource.valuesForPath(definition: SearchParamDefinition): Sequence { + val paths = definition.path.split(SEPARATOR_REGEX) + if (paths.size <= 1) { + return emptySequence() + } + return paths.asSequence().drop(1).fold(sequenceOf(this)) { acc, next -> + getFieldValues(acc, next, definition.type) } + } - /** Returns the name of the method to retrieve the field `fieldName`. */ - private fun getGetterName(fieldName: String, type: String): String { - val baseGetter = GETTER_PREFIX + - fieldName.substring(0, 1).toUpperCase(Locale.US) + - fieldName.substring(1) - when (type) { - "date" -> return baseGetter + GETTER_SUFFIX_DATE - else -> return baseGetter + /** + * Returns the list of field values for `fieldName` in each of the `objects`. + * + * If the field is a [Collection], it will be expanded and each element of the [Collection] will + * be added to the returned value. + */ + private fun getFieldValues( + objects: Sequence, + fieldName: String, + type: String + ): Sequence { + return objects + .asSequence() + .flatMap { + val value = + try { + /* TODO + * Upstream HAPI FHIR returns FHIR date* types from getxxDate methods as + * java.util.Date. For HAPI FHIR BaseDateTimeTypes, which support TimeZones + * we need to invoke() getxxDateElement. Hence we need to pass in search parameter + * type to getGetterName below. + */ + it.javaClass.getMethod(getGetterName(fieldName, type)).invoke(it) + } catch (error: Throwable) { + Log.w(TAG, error) + null + } + if (value is Collection<*>) { + value.asSequence() + } else { + sequenceOf(value) } + } + .filterNotNull() + } + + /** Returns the name of the method to retrieve the field `fieldName`. */ + private fun getGetterName(fieldName: String, type: String): String { + val baseGetter = + GETTER_PREFIX + fieldName.substring(0, 1).toUpperCase(Locale.US) + fieldName.substring(1) + when (type) { + "date" -> return baseGetter + GETTER_SUFFIX_DATE + else -> return baseGetter } + } - /** - * Returns whether the given path only uses a dot notation with no additional expressions such - * as where() or exists(). - */ - @Suppress("NOTHING_TO_INLINE") - private inline fun String.hasDotNotationOnly() = matches(DOT_NOTATION_REGEX) + /** + * Returns whether the given path only uses a dot notation with no additional expressions such as + * where() or exists(). + */ + @Suppress("NOTHING_TO_INLINE") + private inline fun String.hasDotNotationOnly() = matches(DOT_NOTATION_REGEX) - /** The prefix of getter methods for retrieving field values. */ - private const val GETTER_PREFIX = "get" + /** The prefix of getter methods for retrieving field values. */ + private const val GETTER_PREFIX = "get" - /** The suffix of getter methods for retrieving a date 'Element'. */ - private const val GETTER_SUFFIX_DATE = "Element" + /** The suffix of getter methods for retrieving a date 'Element'. */ + private const val GETTER_SUFFIX_DATE = "Element" - /** The regular expression for the separator */ - private val SEPARATOR_REGEX = "\\.".toRegex() + /** The regular expression for the separator */ + private val SEPARATOR_REGEX = "\\.".toRegex() - /** The string representing the string search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_STRING = "string" + /** The string representing the string search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_STRING = "string" - /** The string representing the reference search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_REFERENCE = "reference" + /** The string representing the reference search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_REFERENCE = "reference" - /** The string representing the code search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_CODE = "token" + /** The string representing the code search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_CODE = "token" - /** The string representing the quantity search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_QUANTITY = "quantity" + /** The string representing the quantity search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_QUANTITY = "quantity" - /** The string representing the uri search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_URI = "uri" + /** The string representing the uri search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_URI = "uri" - /** The string representing the date search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_DATE = "date" + /** The string representing the date search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_DATE = "date" - /** The string representing the number search parameter type. */ - private const val SEARCH_PARAM_DEFINITION_TYPE_NUMBER = "number" + /** The string representing the number search parameter type. */ + private const val SEARCH_PARAM_DEFINITION_TYPE_NUMBER = "number" - /** The string for FHIR currency system */ - // See: https://bit.ly/30YB3ML - // See: https://www.hl7.org/fhir/valueset-currencies.html - private const val FHIR_CURRENCY_SYSTEM = "urn:iso:std:iso:4217" + /** The string for FHIR currency system */ + // See: https://bit.ly/30YB3ML + // See: https://www.hl7.org/fhir/valueset-currencies.html + private const val FHIR_CURRENCY_SYSTEM = "urn:iso:std:iso:4217" - /** Tag for logging. */ - private const val TAG = "FhirIndexerImpl" - private val DOT_NOTATION_REGEX = "^[a-zA-Z0-9.]+$".toRegex() + /** Tag for logging. */ + private const val TAG = "FhirIndexerImpl" + private val DOT_NOTATION_REGEX = "^[a-zA-Z0-9.]+$".toRegex() } diff --git a/core/src/main/java/com/google/android/fhir/index/ResourceIndices.kt b/core/src/main/java/com/google/android/fhir/index/ResourceIndices.kt index df0c59c62a..af2638baa0 100644 --- a/core/src/main/java/com/google/android/fhir/index/ResourceIndices.kt +++ b/core/src/main/java/com/google/android/fhir/index/ResourceIndices.kt @@ -32,43 +32,44 @@ import org.hl7.fhir.r4.model.ResourceType * See https://www.hl7.org/fhir/search.html. */ internal data class ResourceIndices( - val resourceType: ResourceType, - val resourceId: String, - val numberIndices: List, - val dateIndices: List, - val stringIndices: List, - val uriIndices: List, - val tokenIndices: List, - val quantityIndices: List, - val referenceIndices: List + val resourceType: ResourceType, + val resourceId: String, + val numberIndices: List, + val dateIndices: List, + val stringIndices: List, + val uriIndices: List, + val tokenIndices: List, + val quantityIndices: List, + val referenceIndices: List ) { - class Builder(private val resourceType: ResourceType, private val resourceId: String) { - private val stringIndices = mutableListOf() - private val referenceIndices = mutableListOf() - private val tokenIndices = mutableListOf() - private val quantityIndices = mutableListOf() - private val uriIndices = mutableListOf() - private val dateIndices = mutableListOf() - private val numberIndices = mutableListOf() + class Builder(private val resourceType: ResourceType, private val resourceId: String) { + private val stringIndices = mutableListOf() + private val referenceIndices = mutableListOf() + private val tokenIndices = mutableListOf() + private val quantityIndices = mutableListOf() + private val uriIndices = mutableListOf() + private val dateIndices = mutableListOf() + private val numberIndices = mutableListOf() - fun addNumberIndex(numberIndex: NumberIndex) = numberIndices.add(numberIndex) - fun addDateIndex(dateIndex: DateIndex) = dateIndices.add(dateIndex) - fun addStringIndex(stringIndex: StringIndex) = stringIndices.add(stringIndex) - fun addUriIndex(uriIndex: UriIndex) = uriIndices.add(uriIndex) - fun addTokenIndex(tokenIndex: TokenIndex) = tokenIndices.add(tokenIndex) - fun addQuantityIndex(quantityIndex: QuantityIndex) = quantityIndices.add(quantityIndex) - fun addReferenceIndex(referenceIndex: ReferenceIndex) = referenceIndices.add(referenceIndex) + fun addNumberIndex(numberIndex: NumberIndex) = numberIndices.add(numberIndex) + fun addDateIndex(dateIndex: DateIndex) = dateIndices.add(dateIndex) + fun addStringIndex(stringIndex: StringIndex) = stringIndices.add(stringIndex) + fun addUriIndex(uriIndex: UriIndex) = uriIndices.add(uriIndex) + fun addTokenIndex(tokenIndex: TokenIndex) = tokenIndices.add(tokenIndex) + fun addQuantityIndex(quantityIndex: QuantityIndex) = quantityIndices.add(quantityIndex) + fun addReferenceIndex(referenceIndex: ReferenceIndex) = referenceIndices.add(referenceIndex) - fun build() = ResourceIndices( - resourceType = resourceType, - resourceId = resourceId, - numberIndices = numberIndices.toList(), - dateIndices = dateIndices.toList(), - stringIndices = stringIndices.toList(), - uriIndices = uriIndices.toList(), - tokenIndices = tokenIndices.toList(), - quantityIndices = quantityIndices.toList(), - referenceIndices = referenceIndices.toList() - ) - } + fun build() = + ResourceIndices( + resourceType = resourceType, + resourceId = resourceId, + numberIndices = numberIndices.toList(), + dateIndices = dateIndices.toList(), + stringIndices = stringIndices.toList(), + uriIndices = uriIndices.toList(), + tokenIndices = tokenIndices.toList(), + quantityIndices = quantityIndices.toList(), + referenceIndices = referenceIndices.toList() + ) + } } diff --git a/core/src/main/java/com/google/android/fhir/index/entities/DateIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/DateIndex.kt index 3aed193202..6541c6ac63 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/DateIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/DateIndex.kt @@ -24,18 +24,17 @@ import ca.uhn.fhir.model.api.TemporalPrecisionEnum * See https://hl7.org/FHIR/search.html#date. */ internal data class DateIndex( - /** The name of the date index, e.g. "birthdate". */ - val name: String, - /** The path of the date index, e.g. "Patient.birthdate". */ - val path: String, - /** The high timestamp. */ - val tsHigh: Long, - /** The low timestamp. */ - val tsLow: Long, - /** - * The smallest value we can unambiguously resolve the date to. - * This is an indication to clients that any part of the timestamp smaller than the - * [temporalPrecision] should be ignored. - */ - val temporalPrecision: TemporalPrecisionEnum + /** The name of the date index, e.g. "birthdate". */ + val name: String, + /** The path of the date index, e.g. "Patient.birthdate". */ + val path: String, + /** The high timestamp. */ + val tsHigh: Long, + /** The low timestamp. */ + val tsLow: Long, + /** + * The smallest value we can unambiguously resolve the date to. This is an indication to clients + * that any part of the timestamp smaller than the [temporalPrecision] should be ignored. + */ + val temporalPrecision: TemporalPrecisionEnum ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/NumberIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/NumberIndex.kt index ca129b747c..2fe87c4417 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/NumberIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/NumberIndex.kt @@ -24,10 +24,10 @@ import java.math.BigDecimal * See https://hl7.org/FHIR/search.html#number. */ internal data class NumberIndex( - /** The name of the number index, e.g. "probability". */ - val name: String, - /** The path of the number index, e.g. "RiskAssessment.​prediction.​probability". */ - val path: String, - /** The value of the number index, e.g. "0.1". */ - val value: BigDecimal + /** The name of the number index, e.g. "probability". */ + val name: String, + /** The path of the number index, e.g. "RiskAssessment.​prediction.​probability". */ + val path: String, + /** The value of the number index, e.g. "0.1". */ + val value: BigDecimal ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/QuantityIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/QuantityIndex.kt index 0c15e6ae28..9cb7ba03af 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/QuantityIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/QuantityIndex.kt @@ -24,9 +24,9 @@ import java.math.BigDecimal * See https://hl7.org/FHIR/search.html#quantity. */ internal data class QuantityIndex( - val name: String, - val path: String, - val system: String, - val unit: String, - val value: BigDecimal + val name: String, + val path: String, + val system: String, + val unit: String, + val value: BigDecimal ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/ReferenceIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/ReferenceIndex.kt index ddb4fb9e30..ab70513ccb 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/ReferenceIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/ReferenceIndex.kt @@ -22,10 +22,10 @@ package com.google.android.fhir.index.entities * See https://hl7.org/FHIR/search.html#reference. */ internal data class ReferenceIndex( - /** The name of the reference index, e.g. "subject". */ - val name: String, - /** The path of the reference index, e.g. "Observation.subject". */ - val path: String, - /** The value of the reference index, e.g. "Patient/123". */ - val value: String + /** The name of the reference index, e.g. "subject". */ + val name: String, + /** The path of the reference index, e.g. "Observation.subject". */ + val path: String, + /** The value of the reference index, e.g. "Patient/123". */ + val value: String ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/StringIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/StringIndex.kt index 7e7ded8978..7d6673487f 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/StringIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/StringIndex.kt @@ -22,10 +22,10 @@ package com.google.android.fhir.index.entities * See https://hl7.org/FHIR/search.html#string. */ internal data class StringIndex( - /** The name of the string index, e.g. "given". */ - val name: String, - /** The path of the string index, e.g. "Patient.name.given". */ - val path: String, - /** The value of the string index, e.g. "Tom". */ - val value: String + /** The name of the string index, e.g. "given". */ + val name: String, + /** The path of the string index, e.g. "Patient.name.given". */ + val path: String, + /** The value of the string index, e.g. "Tom". */ + val value: String ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/TokenIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/TokenIndex.kt index a7452ee5bc..b6a595f1ea 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/TokenIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/TokenIndex.kt @@ -22,12 +22,12 @@ package com.google.android.fhir.index.entities * See https://hl7.org/FHIR/search.html#token. */ internal data class TokenIndex( - /** The name of the code index, e.g. "code". */ - val name: String, - /** The path of the code index, e.g. "Observation.code". */ - val path: String, - /** The system of the code index, e.g. "http://openmrs.org/concepts". */ - val system: String, - /** The value of the code index, e.g. "1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA". */ - val value: String + /** The name of the code index, e.g. "code". */ + val name: String, + /** The path of the code index, e.g. "Observation.code". */ + val path: String, + /** The system of the code index, e.g. "http://openmrs.org/concepts". */ + val system: String, + /** The value of the code index, e.g. "1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA". */ + val value: String ) diff --git a/core/src/main/java/com/google/android/fhir/index/entities/UriIndex.kt b/core/src/main/java/com/google/android/fhir/index/entities/UriIndex.kt index f8ac973bc5..4936db775d 100644 --- a/core/src/main/java/com/google/android/fhir/index/entities/UriIndex.kt +++ b/core/src/main/java/com/google/android/fhir/index/entities/UriIndex.kt @@ -21,8 +21,4 @@ package com.google.android.fhir.index.entities * * See https://hl7.org/FHIR/search.html#uri. */ -internal data class UriIndex( - val name: String, - val path: String, - val uri: String -) +internal data class UriIndex(val name: String, val path: String, val uri: String) diff --git a/core/src/main/java/com/google/android/fhir/resource/Resources.kt b/core/src/main/java/com/google/android/fhir/resource/Resources.kt index 357fbe4254..9e181de335 100644 --- a/core/src/main/java/com/google/android/fhir/resource/Resources.kt +++ b/core/src/main/java/com/google/android/fhir/resource/Resources.kt @@ -25,26 +25,26 @@ internal val R4_RESOURCE_PACKAGE_PREFIX = "org.hl7.fhir.r4.model." /** Returns the FHIR resource type. */ internal fun getResourceType(clazz: Class): ResourceType { - try { - return clazz.getConstructor().newInstance().getResourceType() - } catch (e: NoSuchMethodException) { - throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) - } catch (e: IllegalAccessException) { - throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) - } catch (e: InstantiationException) { - throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) - } catch (e: InvocationTargetException) { - throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) - } + try { + return clazz.getConstructor().newInstance().getResourceType() + } catch (e: NoSuchMethodException) { + throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) + } catch (e: IllegalAccessException) { + throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) + } catch (e: InstantiationException) { + throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) + } catch (e: InvocationTargetException) { + throw IllegalArgumentException("Cannot resolve resource type for " + clazz.getName(), e) + } } /** Returns the {@link Class} object for the resource type. */ internal fun getResourceClass(resourceType: String): Class { - // Remove any curly brackets in the resource type string. This is to work around an issue with - // JSON deserialization in the CQL engine on Android. The resource type string incorrectly - // includes namespace prefix in curly brackets, e.g. "{http://hl7.org/fhir}Patient" instead of - // "Patient". - // TODO: remove this once a fix has been found for the CQL engine on Android. - val className = resourceType.replace(Regex("\\{[^}]*\\}"), "") - return Class.forName(R4_RESOURCE_PACKAGE_PREFIX + className) as Class + // Remove any curly brackets in the resource type string. This is to work around an issue with + // JSON deserialization in the CQL engine on Android. The resource type string incorrectly + // includes namespace prefix in curly brackets, e.g. "{http://hl7.org/fhir}Patient" instead of + // "Patient". + // TODO: remove this once a fix has been found for the CQL engine on Android. + val className = resourceType.replace(Regex("\\{[^}]*\\}"), "") + return Class.forName(R4_RESOURCE_PACKAGE_PREFIX + className) as Class } diff --git a/core/src/main/java/com/google/android/fhir/search/Search.kt b/core/src/main/java/com/google/android/fhir/search/Search.kt index 6b9cb08806..79c3fb6cc9 100644 --- a/core/src/main/java/com/google/android/fhir/search/Search.kt +++ b/core/src/main/java/com/google/android/fhir/search/Search.kt @@ -35,32 +35,24 @@ import org.hl7.fhir.r4.model.Resource * ``` */ interface Search { - /** - * Returns a [SearchSpecifications] object with the given [clazz]. - */ - fun of(clazz: Class): SearchSpecifications + /** Returns a [SearchSpecifications] object with the given [clazz]. */ + fun of(clazz: Class): SearchSpecifications - /** The interface to specify the search criteria and to execute the search. */ - interface SearchSpecifications { - /** - * Returns a [SearchSpecifications] object with the [filterCriterion]. - */ - fun filter(filterCriterion: FilterCriterion): SearchSpecifications + /** The interface to specify the search criteria and to execute the search. */ + interface SearchSpecifications { + /** Returns a [SearchSpecifications] object with the [filterCriterion]. */ + fun filter(filterCriterion: FilterCriterion): SearchSpecifications - /** - * Returns a [SearchSpecifications] object with the [sortCriterion]. - */ - fun sort(sortCriterion: SortCriterion): SearchSpecifications + /** Returns a [SearchSpecifications] object with the [sortCriterion]. */ + fun sort(sortCriterion: SortCriterion): SearchSpecifications - /** - * Returns a [SearchSpecifications] object that only includes the first [limit] results. - */ - fun limit(limit: Int): SearchSpecifications + /** Returns a [SearchSpecifications] object that only includes the first [limit] results. */ + fun limit(limit: Int): SearchSpecifications - /** Returns a [SearchSpecifications] object that skips the first [skip] results. */ - fun skip(skip: Int): SearchSpecifications + /** Returns a [SearchSpecifications] object that skips the first [skip] results. */ + fun skip(skip: Int): SearchSpecifications - /** Runs a search with the [SearchSpecifications]. */ - fun run(): List - } + /** Runs a search with the [SearchSpecifications]. */ + fun run(): List + } } diff --git a/core/src/main/java/com/google/android/fhir/search/filter/AndFilterCriterion.kt b/core/src/main/java/com/google/android/fhir/search/filter/AndFilterCriterion.kt index 809f23b604..6e40836bd6 100644 --- a/core/src/main/java/com/google/android/fhir/search/filter/AndFilterCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/filter/AndFilterCriterion.kt @@ -19,20 +19,17 @@ package com.google.android.fhir.search.filter import com.google.android.fhir.search.impl.ResourceIdQuery import org.hl7.fhir.r4.model.Resource -/** - * [FilterCriterion] that is satisfied if all of the sub [FilterCriterion]s are satisfied. - */ -class AndFilterCriterion constructor( - val left: FilterCriterion, - val right: FilterCriterion -) : FilterCriterion { - override fun query(clazz: Class): ResourceIdQuery { - val leftQuery = left.query(clazz) - val rightQuery = right.query(clazz) - return ResourceIdQuery( - "${leftQuery.query} INTERSECT ${rightQuery.query}", - leftQuery.args + rightQuery.args) - } +/** [FilterCriterion] that is satisfied if all of the sub [FilterCriterion] s are satisfied. */ +class AndFilterCriterion constructor(val left: FilterCriterion, val right: FilterCriterion) : + FilterCriterion { + override fun query(clazz: Class): ResourceIdQuery { + val leftQuery = left.query(clazz) + val rightQuery = right.query(clazz) + return ResourceIdQuery( + "${leftQuery.query} INTERSECT ${rightQuery.query}", + leftQuery.args + rightQuery.args + ) + } } fun and(left: FilterCriterion, right: FilterCriterion) = AndFilterCriterion(left, right) diff --git a/core/src/main/java/com/google/android/fhir/search/filter/FilterCriterion.kt b/core/src/main/java/com/google/android/fhir/search/filter/FilterCriterion.kt index c9c2d80314..53824701dc 100644 --- a/core/src/main/java/com/google/android/fhir/search/filter/FilterCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/filter/FilterCriterion.kt @@ -21,12 +21,10 @@ import org.hl7.fhir.r4.model.Resource /** Interface to specify filtering criteria for search. */ interface FilterCriterion { - fun and(filterCriterion: FilterCriterion): FilterCriterion = - and(this, filterCriterion) + fun and(filterCriterion: FilterCriterion): FilterCriterion = and(this, filterCriterion) - fun or(filterCriterion: FilterCriterion): FilterCriterion = - or(this, filterCriterion) + fun or(filterCriterion: FilterCriterion): FilterCriterion = or(this, filterCriterion) - /** Returns the [ResourceIdQuery] that can be used to construct a query. */ - fun query(clazz: Class): ResourceIdQuery + /** Returns the [ResourceIdQuery] that can be used to construct a query. */ + fun query(clazz: Class): ResourceIdQuery } diff --git a/core/src/main/java/com/google/android/fhir/search/filter/OrFilterCriterion.kt b/core/src/main/java/com/google/android/fhir/search/filter/OrFilterCriterion.kt index ba34c7b264..ace70411b7 100644 --- a/core/src/main/java/com/google/android/fhir/search/filter/OrFilterCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/filter/OrFilterCriterion.kt @@ -19,18 +19,17 @@ package com.google.android.fhir.search.filter import com.google.android.fhir.search.impl.ResourceIdQuery import org.hl7.fhir.r4.model.Resource -/** - * [FilterCriterion] that is satisfied if any of the sub [FilterCriterion]s is satisfied. - */ +/** [FilterCriterion] that is satisfied if any of the sub [FilterCriterion] s is satisfied. */ class OrFilterCriterion constructor(val left: FilterCriterion, val right: FilterCriterion) : - FilterCriterion { - override fun query(clazz: Class): ResourceIdQuery { - val leftQuery = left.query(clazz) - val rightQuery = right.query(clazz) - return ResourceIdQuery( - "${leftQuery.query} UNION ${rightQuery.query}", - leftQuery.args + rightQuery.args) - } + FilterCriterion { + override fun query(clazz: Class): ResourceIdQuery { + val leftQuery = left.query(clazz) + val rightQuery = right.query(clazz) + return ResourceIdQuery( + "${leftQuery.query} UNION ${rightQuery.query}", + leftQuery.args + rightQuery.args + ) + } } fun or(left: FilterCriterion, right: FilterCriterion) = OrFilterCriterion(left, right) diff --git a/core/src/main/java/com/google/android/fhir/search/filter/ReferenceFilterCriterion.kt b/core/src/main/java/com/google/android/fhir/search/filter/ReferenceFilterCriterion.kt index 4c1d89b7a5..b45d124c4e 100644 --- a/core/src/main/java/com/google/android/fhir/search/filter/ReferenceFilterCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/filter/ReferenceFilterCriterion.kt @@ -21,17 +21,17 @@ import com.google.android.fhir.search.impl.ResourceIdQuery import org.hl7.fhir.r4.model.Resource /** [FilterCriterion] on a reference value. */ -class ReferenceFilterCriterion constructor( - val param: ReferenceClientParam, - val value: String -) : FilterCriterion { - override fun query(clazz: Class): ResourceIdQuery { - // TODO: implement different queries for different operators. - return ResourceIdQuery(""" +class ReferenceFilterCriterion constructor(val param: ReferenceClientParam, val value: String) : + FilterCriterion { + override fun query(clazz: Class): ResourceIdQuery { + // TODO: implement different queries for different operators. + return ResourceIdQuery( + """ SELECT resourceId FROM ReferenceIndexEntity WHERE resourceType = ? AND index_name = ? AND index_value = ?""".trimIndent(), - listOf(clazz.simpleName, param.paramName, value)) - } + listOf(clazz.simpleName, param.paramName, value) + ) + } } fun reference(param: ReferenceClientParam, value: String) = ReferenceFilterCriterion(param, value) diff --git a/core/src/main/java/com/google/android/fhir/search/filter/StringFilterCriterion.kt b/core/src/main/java/com/google/android/fhir/search/filter/StringFilterCriterion.kt index d4af1d1961..c530904c45 100644 --- a/core/src/main/java/com/google/android/fhir/search/filter/StringFilterCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/filter/StringFilterCriterion.kt @@ -28,19 +28,19 @@ import org.hl7.fhir.r4.model.Resource * * name that matches 'Tom' * * address that includes 'London' */ -class StringFilteringCriterion constructor( - val param: StringClientParam, - val operator: ParamPrefixEnum, - val value: String -) : FilterCriterion { - override fun query(clazz: Class): ResourceIdQuery { - // TODO: implement different queries for different operators. - return ResourceIdQuery(""" +class StringFilteringCriterion +constructor(val param: StringClientParam, val operator: ParamPrefixEnum, val value: String) : + FilterCriterion { + override fun query(clazz: Class): ResourceIdQuery { + // TODO: implement different queries for different operators. + return ResourceIdQuery( + """ SELECT resourceId FROM StringIndexEntity WHERE resourceType = ? AND index_name = ? AND index_value = ?""", - listOf(clazz.simpleName, param.paramName, value)) - } + listOf(clazz.simpleName, param.paramName, value) + ) + } } fun string(param: StringClientParam, operator: ParamPrefixEnum, value: String) = - StringFilteringCriterion(param, operator, value) + StringFilteringCriterion(param, operator, value) diff --git a/core/src/main/java/com/google/android/fhir/search/impl/Query.kt b/core/src/main/java/com/google/android/fhir/search/impl/Query.kt index 187b1d9675..50db0770c7 100644 --- a/core/src/main/java/com/google/android/fhir/search/impl/Query.kt +++ b/core/src/main/java/com/google/android/fhir/search/impl/Query.kt @@ -21,8 +21,8 @@ import androidx.sqlite.db.SupportSQLiteQuery /** Query that returns a list of resource IDs. */ abstract class Query { - abstract fun getQueryString(): String - abstract fun getQueryArgs(): List - fun getSupportSQLiteQuery(): SupportSQLiteQuery = - SimpleSQLiteQuery(getQueryString(), getQueryArgs().toTypedArray()) + abstract fun getQueryString(): String + abstract fun getQueryArgs(): List + fun getSupportSQLiteQuery(): SupportSQLiteQuery = + SimpleSQLiteQuery(getQueryString(), getQueryArgs().toTypedArray()) } diff --git a/core/src/main/java/com/google/android/fhir/search/impl/ResourceIdQuery.kt b/core/src/main/java/com/google/android/fhir/search/impl/ResourceIdQuery.kt index 5682499f7f..fd72ed9e3a 100644 --- a/core/src/main/java/com/google/android/fhir/search/impl/ResourceIdQuery.kt +++ b/core/src/main/java/com/google/android/fhir/search/impl/ResourceIdQuery.kt @@ -18,6 +18,6 @@ package com.google.android.fhir.search.impl /** Query that returns a list of resource IDs. */ data class ResourceIdQuery(val query: String, val args: List) : Query() { - override fun getQueryString(): String = query - override fun getQueryArgs(): List = args + override fun getQueryString(): String = query + override fun getQueryArgs(): List = args } diff --git a/core/src/main/java/com/google/android/fhir/search/impl/SearchImpl.kt b/core/src/main/java/com/google/android/fhir/search/impl/SearchImpl.kt index beb0250fad..c86f20aaa7 100644 --- a/core/src/main/java/com/google/android/fhir/search/impl/SearchImpl.kt +++ b/core/src/main/java/com/google/android/fhir/search/impl/SearchImpl.kt @@ -25,29 +25,37 @@ import org.hl7.fhir.r4.model.Resource /** Implementation of the [Search] interface. */ class SearchImpl constructor(val database: Database) : Search { - override fun of(clazz: Class) = SearchSpecificationImpl(clazz) - - /** Implementation of the [Search.SearchSpecifications] interface. */ - inner class SearchSpecificationImpl( - val clazz: Class - ) : Search.SearchSpecifications { - private var filterCriterion: FilterCriterion? = null - private var sortCriterion: SortCriterion? = null - private var limit: Int? = null - private var skip: Int? = null - - override fun filter(filterCriterion: FilterCriterion): Search.SearchSpecifications = - apply { this.filterCriterion = filterCriterion } - - override fun sort(sortCriterion: SortCriterion): Search.SearchSpecifications = - apply { this.sortCriterion = sortCriterion } - - override fun limit(limit: Int): Search.SearchSpecifications = apply { this.limit = limit } - - override fun skip(skip: Int): Search.SearchSpecifications = apply { this.skip = skip } + override fun of(clazz: Class) = SearchSpecificationImpl(clazz) + + /** Implementation of the [Search.SearchSpecifications] interface. */ + inner class SearchSpecificationImpl(val clazz: Class) : + Search.SearchSpecifications { + private var filterCriterion: FilterCriterion? = null + private var sortCriterion: SortCriterion? = null + private var limit: Int? = null + private var skip: Int? = null + + override fun filter(filterCriterion: FilterCriterion): Search.SearchSpecifications = apply { + this.filterCriterion = filterCriterion + } - override fun run(): List = database.search( - SerializedResourceQuery(getResourceType(clazz), - filterCriterion?.query(clazz), sortCriterion, limit, skip)) + override fun sort(sortCriterion: SortCriterion): Search.SearchSpecifications = apply { + this.sortCriterion = sortCriterion } + + override fun limit(limit: Int): Search.SearchSpecifications = apply { this.limit = limit } + + override fun skip(skip: Int): Search.SearchSpecifications = apply { this.skip = skip } + + override fun run(): List = + database.search( + SerializedResourceQuery( + getResourceType(clazz), + filterCriterion?.query(clazz), + sortCriterion, + limit, + skip + ) + ) + } } diff --git a/core/src/main/java/com/google/android/fhir/search/impl/SerializedResourceQuery.kt b/core/src/main/java/com/google/android/fhir/search/impl/SerializedResourceQuery.kt index 90be4d54a9..58fc2b2f1f 100644 --- a/core/src/main/java/com/google/android/fhir/search/impl/SerializedResourceQuery.kt +++ b/core/src/main/java/com/google/android/fhir/search/impl/SerializedResourceQuery.kt @@ -21,30 +21,36 @@ import org.hl7.fhir.r4.model.ResourceType /** Query that returns a list of serialized resources. */ data class SerializedResourceQuery( - val resourceType: ResourceType, - val resourceIdQuery: ResourceIdQuery?, - val sortCriterion: SortCriterion?, - val limit: Int?, - val skip: Int? + val resourceType: ResourceType, + val resourceIdQuery: ResourceIdQuery?, + val sortCriterion: SortCriterion?, + val limit: Int?, + val skip: Int? ) : Query() { - override fun getQueryString(): String { - val queryBuilder = StringBuilder() - queryBuilder.appendln(""" + override fun getQueryString(): String { + val queryBuilder = StringBuilder() + queryBuilder.appendln( + """ SELECT a.serializedResource FROM ResourceEntity a - """.trimIndent()) - sortCriterion?.also { - queryBuilder.appendln(""" + """.trimIndent() + ) + sortCriterion?.also { + queryBuilder.appendln( + """ LEFT JOIN ${sortCriterion.table} b ON a.resourceType = b.resourceType AND a.resourceId = b.resourceId AND b.index_name = ? - """.trimIndent()) - } - queryBuilder.appendln(""" + """.trimIndent() + ) + } + queryBuilder.appendln( + """ WHERE a.resourceType = ?${resourceIdQuery?.let { " AND a.resourceId IN (${it.query})" } ?: ""} - """.trimIndent()) - sortCriterion?.also { - queryBuilder.appendln( - """ + """.trimIndent() + ) + sortCriterion?.also { + queryBuilder.appendln( + """ ORDER BY b.index_value ${ if (sortCriterion.ascending) { "ASC" @@ -52,30 +58,32 @@ data class SerializedResourceQuery( "DESC" } } - """.trimIndent()) - } - limit?.also { - queryBuilder.appendln(""" + """.trimIndent() + ) + } + limit?.also { + queryBuilder.appendln( + """ LIMIT ?${skip?.let { " OFFSET ?" } ?: ""} - """.trimIndent()) - } - return queryBuilder.toString().trimIndent() + """.trimIndent() + ) } + return queryBuilder.toString().trimIndent() + } - override fun getQueryArgs(): List { - var list: List = if (sortCriterion == null) { - listOf() - } else { - listOf(sortCriterion.param) - } - list = list + resourceType.name - resourceIdQuery?.also { list = list + it.getQueryArgs() } - limit?.also { - list = list + it - skip?.also { - list = list + it - } - } - return list + override fun getQueryArgs(): List { + var list: List = + if (sortCriterion == null) { + listOf() + } else { + listOf(sortCriterion.param) + } + list = list + resourceType.name + resourceIdQuery?.also { list = list + it.getQueryArgs() } + limit?.also { + list = list + it + skip?.also { list = list + it } } + return list + } } diff --git a/core/src/main/java/com/google/android/fhir/search/sort/SortCriterion.kt b/core/src/main/java/com/google/android/fhir/search/sort/SortCriterion.kt index 326f913b3c..cb1c889501 100644 --- a/core/src/main/java/com/google/android/fhir/search/sort/SortCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/sort/SortCriterion.kt @@ -18,7 +18,7 @@ package com.google.android.fhir.search.sort /** Interface to specify filtering criteria for search. */ interface SortCriterion { - val table: String - val param: String - val ascending: Boolean + val table: String + val param: String + val ascending: Boolean } diff --git a/core/src/main/java/com/google/android/fhir/search/sort/StringSortCriterion.kt b/core/src/main/java/com/google/android/fhir/search/sort/StringSortCriterion.kt index 3f32f2cd44..0e5af3ce90 100644 --- a/core/src/main/java/com/google/android/fhir/search/sort/StringSortCriterion.kt +++ b/core/src/main/java/com/google/android/fhir/search/sort/StringSortCriterion.kt @@ -26,13 +26,10 @@ import com.google.android.fhir.search.filter.FilterCriterion * * name that matches 'Tom' * * address that includes 'London' */ -data class StringSortCriterion constructor( - val stringParam: StringClientParam, - override val ascending: Boolean -) : SortCriterion { - override val table: String = "StringIndexEntity" - override val param = stringParam.paramName +data class StringSortCriterion +constructor(val stringParam: StringClientParam, override val ascending: Boolean) : SortCriterion { + override val table: String = "StringIndexEntity" + override val param = stringParam.paramName } -fun stringSort(param: StringClientParam, ascending: Boolean) = - StringSortCriterion(param, ascending) +fun stringSort(param: StringClientParam, ascending: Boolean) = StringSortCriterion(param, ascending) diff --git a/core/src/main/java/com/google/android/fhir/sync/FhirDataSource.kt b/core/src/main/java/com/google/android/fhir/sync/FhirDataSource.kt index 94f555af05..fbaddde706 100644 --- a/core/src/main/java/com/google/android/fhir/sync/FhirDataSource.kt +++ b/core/src/main/java/com/google/android/fhir/sync/FhirDataSource.kt @@ -24,9 +24,9 @@ import org.hl7.fhir.r4.model.Bundle */ interface FhirDataSource { - /** - * Implement this method to load remote data based on a url [path]. - * A service base url is of the form: `http{s}://server/{path}` - */ - suspend fun loadData(path: String): Bundle + /** + * Implement this method to load remote data based on a url [path]. A service base url is of the + * form: `http{s}://server/{path}` + */ + suspend fun loadData(path: String): Bundle } diff --git a/core/src/main/java/com/google/android/fhir/sync/FhirSynchronizer.kt b/core/src/main/java/com/google/android/fhir/sync/FhirSynchronizer.kt index 386cec9d04..0bbf924807 100644 --- a/core/src/main/java/com/google/android/fhir/sync/FhirSynchronizer.kt +++ b/core/src/main/java/com/google/android/fhir/sync/FhirSynchronizer.kt @@ -20,39 +20,33 @@ import com.google.android.fhir.db.Database import org.hl7.fhir.r4.model.ResourceType sealed class Result { - object Success : Result() - data class Error(val exceptions: List) : Result() + object Success : Result() + data class Error(val exceptions: List) : Result() } data class ResourceSyncException(val resourceType: ResourceType, val exception: Exception) -/** - * Class that helps synchronize the data source and save it in the local database - */ +/** Class that helps synchronize the data source and save it in the local database */ class FhirSynchronizer( - private val syncConfiguration: SyncConfiguration, - private val dataSource: FhirDataSource, - private val database: Database + private val syncConfiguration: SyncConfiguration, + private val dataSource: FhirDataSource, + private val database: Database ) { - suspend fun sync(): Result { - val exceptions = mutableListOf() - syncConfiguration.syncData.forEach { syncData -> - val resourceSynchroniser = ResourceSynchronizer( - syncData, - dataSource, - database, - syncConfiguration.retry - ) - try { - resourceSynchroniser.sync() - } catch (exception: Exception) { - exceptions.add(ResourceSyncException(syncData.resourceType, exception)) - } - } - if (exceptions.isEmpty()) { - return Result.Success - } else { - return Result.Error(exceptions) - } + suspend fun sync(): Result { + val exceptions = mutableListOf() + syncConfiguration.syncData.forEach { syncData -> + val resourceSynchroniser = + ResourceSynchronizer(syncData, dataSource, database, syncConfiguration.retry) + try { + resourceSynchroniser.sync() + } catch (exception: Exception) { + exceptions.add(ResourceSyncException(syncData.resourceType, exception)) + } + } + if (exceptions.isEmpty()) { + return Result.Success + } else { + return Result.Error(exceptions) } + } } diff --git a/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncConfiguration.kt b/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncConfiguration.kt index cf94113a71..cbfb95dbb5 100644 --- a/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncConfiguration.kt +++ b/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncConfiguration.kt @@ -19,36 +19,26 @@ package com.google.android.fhir.sync import androidx.work.Constraints import java.util.concurrent.TimeUnit -/** - * Configuration for period synchronisation - */ +/** Configuration for period synchronisation */ class PeriodicSyncConfiguration( - val syncConfiguration: SyncConfiguration, - /** - * Constraints that specify the requirements needed before the synchronisation is triggered. - * E.g. network type (Wifi, 3G etc), the device should be charging etc. - */ - val syncConstraints: Constraints = Constraints.Builder().build(), + val syncConfiguration: SyncConfiguration, + /** + * Constraints that specify the requirements needed before the synchronisation is triggered. E.g. + * network type (Wifi, 3G etc), the device should be charging etc. + */ + val syncConstraints: Constraints = Constraints.Builder().build(), - /** - * Worker that will execute the periodic sync - */ - val periodicSyncWorker: Class, + /** Worker that will execute the periodic sync */ + val periodicSyncWorker: Class, - /** - * The interval at which the sync should be triggered in - */ - val repeat: RepeatInterval + /** The interval at which the sync should be triggered in */ + val repeat: RepeatInterval ) data class RepeatInterval( - /** - * The interval at which the sync should be triggered in - */ - val interval: Long, + /** The interval at which the sync should be triggered in */ + val interval: Long, - /** - * The time unit for the repeat interval - */ - val timeUnit: TimeUnit + /** The time unit for the repeat interval */ + val timeUnit: TimeUnit ) diff --git a/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncWorker.kt b/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncWorker.kt index a8fc98c47a..88e9d1ac9c 100644 --- a/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncWorker.kt +++ b/core/src/main/java/com/google/android/fhir/sync/PeriodicSyncWorker.kt @@ -22,22 +22,18 @@ import androidx.work.WorkerParameters import com.google.android.fhir.FhirEngine import com.google.android.fhir.sync.Result.Success -/** - * A WorkManager Worker that handles periodic sync. - */ -abstract class PeriodicSyncWorker( - appContext: Context, - workerParams: WorkerParameters -) : CoroutineWorker(appContext, workerParams) { +/** A WorkManager Worker that handles periodic sync. */ +abstract class PeriodicSyncWorker(appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { - abstract fun getFhirEngine(): FhirEngine + abstract fun getFhirEngine(): FhirEngine - override suspend fun doWork(): Result { - // TODO handle retry - val result = getFhirEngine().periodicSync() - if (result is Success) { - return Result.success() - } - return Result.failure() + override suspend fun doWork(): Result { + // TODO handle retry + val result = getFhirEngine().periodicSync() + if (result is Success) { + return Result.success() } + return Result.failure() + } } diff --git a/core/src/main/java/com/google/android/fhir/sync/ResourceSynchronizer.kt b/core/src/main/java/com/google/android/fhir/sync/ResourceSynchronizer.kt index 696ed85857..31bcd71365 100644 --- a/core/src/main/java/com/google/android/fhir/sync/ResourceSynchronizer.kt +++ b/core/src/main/java/com/google/android/fhir/sync/ResourceSynchronizer.kt @@ -25,73 +25,71 @@ import com.google.android.fhir.toTimeZoneString import java.io.IOException import org.hl7.fhir.r4.model.Bundle -/** - * Class that synchronises only one resource. - */ +/** Class that synchronises only one resource. */ class ResourceSynchronizer( - private val syncData: SyncData, - private val dataSource: FhirDataSource, - private val database: Database, - retry: Boolean + private val syncData: SyncData, + private val dataSource: FhirDataSource, + private val database: Database, + retry: Boolean ) { - private var retrySync = retry + private var retrySync = retry - suspend fun sync() { - var nextUrl: String? = getInitialUrl() - try { - while (nextUrl != null) { - val bundle = dataSource.loadData(nextUrl) - nextUrl = bundle.link.firstOrNull { component -> component.relation == "next" }?.url - if (bundle.type == Bundle.BundleType.SEARCHSET) { - saveSyncedResource(bundle) - } - } - } catch (exception: IOException) { - if (retrySync) { - retrySync = false - sync() - } else { - // propagate the exception upstream - throw exception - } + suspend fun sync() { + var nextUrl: String? = getInitialUrl() + try { + while (nextUrl != null) { + val bundle = dataSource.loadData(nextUrl) + nextUrl = bundle.link.firstOrNull { component -> component.relation == "next" }?.url + if (bundle.type == Bundle.BundleType.SEARCHSET) { + saveSyncedResource(bundle) } + } + } catch (exception: IOException) { + if (retrySync) { + retrySync = false + sync() + } else { + // propagate the exception upstream + throw exception + } } + } - private suspend fun getInitialUrl(): String? { - val updatedSyncData = syncData - .addSortParam() - .addLastUpdateDate() - return "${updatedSyncData.resourceType.name}?${updatedSyncData.concatParams()}" - } + private suspend fun getInitialUrl(): String? { + val updatedSyncData = syncData.addSortParam().addLastUpdateDate() + return "${updatedSyncData.resourceType.name}?${updatedSyncData.concatParams()}" + } - private fun SyncData.addSortParam(): SyncData { - if (params.containsKey(SORT_KEY)) { - return this - } - val newParams = params.toMutableMap() - newParams[SORT_KEY] = LAST_UPDATED_ASC_VALUE - return SyncData(resourceType, newParams) + private fun SyncData.addSortParam(): SyncData { + if (params.containsKey(SORT_KEY)) { + return this } + val newParams = params.toMutableMap() + newParams[SORT_KEY] = LAST_UPDATED_ASC_VALUE + return SyncData(resourceType, newParams) + } - private suspend fun SyncData.addLastUpdateDate(): SyncData { - val lastUpdate = database.lastUpdate(resourceType) - if (lastUpdate == null) { - return this - } - val newParams = params.toMutableMap() - newParams[LAST_UPDATED_KEY] = "gt$lastUpdate" - return SyncData(resourceType, newParams) + private suspend fun SyncData.addLastUpdateDate(): SyncData { + val lastUpdate = database.lastUpdate(resourceType) + if (lastUpdate == null) { + return this } + val newParams = params.toMutableMap() + newParams[LAST_UPDATED_KEY] = "gt$lastUpdate" + return SyncData(resourceType, newParams) + } - private suspend fun saveSyncedResource(bundle: Bundle) { - val resources = bundle.entry.map { it.resource } - if (resources.isNotEmpty()) { - val mostRecentResource = resources[resources.lastIndex] - database.insertSyncedResources(SyncedResourceEntity( - syncData.resourceType, - mostRecentResource.meta.lastUpdated.toTimeZoneString()), - resources - ) - } + private suspend fun saveSyncedResource(bundle: Bundle) { + val resources = bundle.entry.map { it.resource } + if (resources.isNotEmpty()) { + val mostRecentResource = resources[resources.lastIndex] + database.insertSyncedResources( + SyncedResourceEntity( + syncData.resourceType, + mostRecentResource.meta.lastUpdated.toTimeZoneString() + ), + resources + ) } + } } diff --git a/core/src/main/java/com/google/android/fhir/sync/SyncConfiguration.kt b/core/src/main/java/com/google/android/fhir/sync/SyncConfiguration.kt index c049e98d29..4e664eb412 100644 --- a/core/src/main/java/com/google/android/fhir/sync/SyncConfiguration.kt +++ b/core/src/main/java/com/google/android/fhir/sync/SyncConfiguration.kt @@ -16,17 +16,13 @@ package com.google.android.fhir.sync -/** - * Configuration for synchronization. - */ +/** Configuration for synchronization. */ data class SyncConfiguration( - /** - * Data that needs to be synchronised - */ - val syncData: List = emptyList(), - /** - * true if the SDK needs to retry a failed sync attempt, false otherwise - * If this is set to true, then the result of the sync will be reported after the retry. - */ - val retry: Boolean = false + /** Data that needs to be synchronised */ + val syncData: List = emptyList(), + /** + * true if the SDK needs to retry a failed sync attempt, false otherwise If this is set to true, + * then the result of the sync will be reported after the retry. + */ + val retry: Boolean = false ) diff --git a/core/src/main/java/com/google/android/fhir/sync/SyncData.kt b/core/src/main/java/com/google/android/fhir/sync/SyncData.kt index 59e3df3340..d43d068e92 100644 --- a/core/src/main/java/com/google/android/fhir/sync/SyncData.kt +++ b/core/src/main/java/com/google/android/fhir/sync/SyncData.kt @@ -20,25 +20,21 @@ import java.net.URLEncoder import org.hl7.fhir.r4.model.ResourceType fun SyncData.concatParams(): String { - return this.params.entries.joinToString("&") { (key, value) -> - "$key=${URLEncoder.encode(value, "UTF-8")}" - } + return this.params.entries.joinToString("&") { (key, value) -> + "$key=${URLEncoder.encode(value, "UTF-8")}" + } } /** * Class that holds what type of resources we need to synchronise and what are the parameters of - * that type. - * e.g. we only want to synchronise patients that live in United States - * `SyncData(ResourceType.Patient, mapOf("address-country" to "United States")` + * that type. e.g. we only want to synchronise patients that live in United States + * `SyncData(ResourceType.Patient, mapOf("address-country" to "United States")` */ -data class SyncData( - val resourceType: ResourceType, - val params: Map = emptyMap() -) { - companion object { - const val SORT_KEY = "_sort" - const val LAST_UPDATED_KEY = "_lastUpdated" - const val ADDRESS_COUNTRY_KEY = "address-country" - const val LAST_UPDATED_ASC_VALUE = "_lastUpdated" - } +data class SyncData(val resourceType: ResourceType, val params: Map = emptyMap()) { + companion object { + const val SORT_KEY = "_sort" + const val LAST_UPDATED_KEY = "_lastUpdated" + const val ADDRESS_COUNTRY_KEY = "address-country" + const val LAST_UPDATED_ASC_VALUE = "_lastUpdated" + } } diff --git a/core/src/main/java/com/google/android/fhir/sync/SyncWorkType.kt b/core/src/main/java/com/google/android/fhir/sync/SyncWorkType.kt index f5b7cef073..b3e1d6e73a 100644 --- a/core/src/main/java/com/google/android/fhir/sync/SyncWorkType.kt +++ b/core/src/main/java/com/google/android/fhir/sync/SyncWorkType.kt @@ -16,10 +16,8 @@ package com.google.android.fhir.sync -/** - * Defines different types of synchronisation workers: download and upload - */ +/** Defines different types of synchronisation workers: download and upload */ internal enum class SyncWorkType(val workerName: String) { - DOWNLOAD("download"), - UPLOAD("upload") + DOWNLOAD("download"), + UPLOAD("upload") } diff --git a/core/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt b/core/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt index 969001eafb..14e2d0e51a 100644 --- a/core/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt +++ b/core/src/test-common/java/com/google/android/fhir/resource/TestingUtils.kt @@ -22,45 +22,45 @@ import org.hl7.fhir.r4.model.Resource import org.json.JSONArray import org.json.JSONObject -/** Utilities for testing. */ +/** Utilities for testing. */ class TestingUtils constructor(private val iParser: IParser) { - /** Asserts that the `expected` and the `actual` FHIR resources are equal. */ - fun assertResourceEquals(expected: Resource?, actual: Resource?) { - Truth.assertThat(iParser.encodeResourceToString(actual)) - .isEqualTo(iParser.encodeResourceToString(expected)) - } + /** Asserts that the `expected` and the `actual` FHIR resources are equal. */ + fun assertResourceEquals(expected: Resource?, actual: Resource?) { + Truth.assertThat(iParser.encodeResourceToString(actual)) + .isEqualTo(iParser.encodeResourceToString(expected)) + } - fun assertJsonArrayEqualsIgnoringOrder(actual: JSONArray, expected: JSONArray) { - Truth.assertThat(actual.length()).isEqualTo(expected.length()) - val actuals = mutableListOf() - val expecteds = mutableListOf() - for (i in 0 until actual.length()) { - actuals.add(actual.get(i).toString()) - expecteds.add(expected.get(i).toString()) - } - actuals.sorted() - expecteds.sorted() - Truth.assertThat(actuals).containsExactlyElementsIn(expecteds) + fun assertJsonArrayEqualsIgnoringOrder(actual: JSONArray, expected: JSONArray) { + Truth.assertThat(actual.length()).isEqualTo(expected.length()) + val actuals = mutableListOf() + val expecteds = mutableListOf() + for (i in 0 until actual.length()) { + actuals.add(actual.get(i).toString()) + expecteds.add(expected.get(i).toString()) } + actuals.sorted() + expecteds.sorted() + Truth.assertThat(actuals).containsExactlyElementsIn(expecteds) + } - /** Reads a [Resource] from given file in the `sampledata` dir */ - fun readFromFile(clazz: Class, filename: String): R { - val resourceJson = readJsonFromFile(filename) - return iParser.parseResource(clazz, resourceJson.toString()) as R - } + /** Reads a [Resource] from given file in the `sampledata` dir */ + fun readFromFile(clazz: Class, filename: String): R { + val resourceJson = readJsonFromFile(filename) + return iParser.parseResource(clazz, resourceJson.toString()) as R + } - /** Reads a [JSONObject] from given file in the `sampledata` dir */ - fun readJsonFromFile(filename: String): JSONObject { - val inputStream = javaClass.getResourceAsStream(filename) - val content = inputStream!!.bufferedReader(Charsets.UTF_8).readText() - return JSONObject(content) - } + /** Reads a [JSONObject] from given file in the `sampledata` dir */ + fun readJsonFromFile(filename: String): JSONObject { + val inputStream = javaClass.getResourceAsStream(filename) + val content = inputStream!!.bufferedReader(Charsets.UTF_8).readText() + return JSONObject(content) + } - /** Reads a [JSONArray] from given file in the `sampledata` dir */ - fun readJsonArrayFromFile(filename: String): JSONArray { - val inputStream = javaClass.getResourceAsStream(filename) - val content = inputStream!!.bufferedReader(Charsets.UTF_8).readText() - return JSONArray(content) - } + /** Reads a [JSONArray] from given file in the `sampledata` dir */ + fun readJsonArrayFromFile(filename: String): JSONArray { + val inputStream = javaClass.getResourceAsStream(filename) + val content = inputStream!!.bufferedReader(Charsets.UTF_8).readText() + return JSONArray(content) + } } diff --git a/core/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt b/core/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt index 37bf5f9795..a4810c999e 100644 --- a/core/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt +++ b/core/src/test/java/com/google/android/fhir/impl/FhirEngineImplTest.kt @@ -33,104 +33,107 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -/** Unit tests for [FhirEngineImpl]. */ +/** Unit tests for [FhirEngineImpl]. */ @RunWith(RobolectricTestRunner::class) class FhirEngineImplTest { - private val dataSource = object : FhirDataSource { - override suspend fun loadData(path: String): Bundle { - return Bundle() - } + private val dataSource = + object : FhirDataSource { + override suspend fun loadData(path: String): Bundle { + return Bundle() + } } - private val services = builder(dataSource, - ApplicationProvider.getApplicationContext()) - .inMemory() - .build() - private val fhirEngine = services.fhirEngine - private val testingUtils = TestingUtils(services.parser) + private val services = + builder(dataSource, ApplicationProvider.getApplicationContext()).inMemory().build() + private val fhirEngine = services.fhirEngine + private val testingUtils = TestingUtils(services.parser) - @Before - fun setUp() { - fhirEngine.save(TEST_PATIENT_1) - } + @Before + fun setUp() { + fhirEngine.save(TEST_PATIENT_1) + } - @Test - fun save_shouldSaveResource() { - fhirEngine.save(TEST_PATIENT_2) - testingUtils.assertResourceEquals( - TEST_PATIENT_2, - fhirEngine.load(Patient::class.java, TEST_PATIENT_2_ID) - ) - } + @Test + fun save_shouldSaveResource() { + fhirEngine.save(TEST_PATIENT_2) + testingUtils.assertResourceEquals( + TEST_PATIENT_2, + fhirEngine.load(Patient::class.java, TEST_PATIENT_2_ID) + ) + } - @Test - fun saveAll_shouldSaveResource() { - val patients = listOf(TEST_PATIENT_1, TEST_PATIENT_2) - fhirEngine.saveAll(patients) - testingUtils.assertResourceEquals( - TEST_PATIENT_1, - fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) - ) - testingUtils.assertResourceEquals( - TEST_PATIENT_2, - fhirEngine.load(Patient::class.java, TEST_PATIENT_2_ID) - ) - } + @Test + fun saveAll_shouldSaveResource() { + val patients = listOf(TEST_PATIENT_1, TEST_PATIENT_2) + fhirEngine.saveAll(patients) + testingUtils.assertResourceEquals( + TEST_PATIENT_1, + fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) + ) + testingUtils.assertResourceEquals( + TEST_PATIENT_2, + fhirEngine.load(Patient::class.java, TEST_PATIENT_2_ID) + ) + } - @Test - fun update_nonexistentResource_shouldNotInsertResource() { - val exception = assertThrows(ResourceNotFoundInDbException::class.java) { - fhirEngine.update(TEST_PATIENT_2) - } - /* ktlint-disable max-line-length */ - Truth.assertThat(exception.message) - .isEqualTo("Resource not found with type ${TEST_PATIENT_2.resourceType.name} and id $TEST_PATIENT_2_ID!") - /* ktlint-enable max-line-length */ - } + @Test + fun update_nonexistentResource_shouldNotInsertResource() { + val exception = + assertThrows(ResourceNotFoundInDbException::class.java) { fhirEngine.update(TEST_PATIENT_2) } + /* ktlint-disable max-line-length */ + Truth.assertThat(exception.message) + .isEqualTo( + "Resource not found with type ${TEST_PATIENT_2.resourceType.name} and id $TEST_PATIENT_2_ID!" + ) + /* ktlint-enable max-line-length */ + } - @Test - fun update_shouldUpdateResource() { - val patient = Patient() - patient.id = TEST_PATIENT_1_ID - patient.gender = Enumerations.AdministrativeGender.FEMALE - fhirEngine.update(patient) - testingUtils.assertResourceEquals( - patient, - fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) - ) - } + @Test + fun update_shouldUpdateResource() { + val patient = Patient() + patient.id = TEST_PATIENT_1_ID + patient.gender = Enumerations.AdministrativeGender.FEMALE + fhirEngine.update(patient) + testingUtils.assertResourceEquals( + patient, + fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) + ) + } - @Test - fun load_nonexistentResource_shouldThrowResourceNotFoundException() { - val resourceNotFoundException = assertThrows(ResourceNotFoundException::class.java) { - fhirEngine.load(Patient::class.java, "nonexistent_patient") - } - /* ktlint-disable max-line-length */ - Truth.assertThat(resourceNotFoundException.message) - .isEqualTo("Resource not found with type ${ResourceType.Patient.name} and id nonexistent_patient!") - /* ktlint-enable max-line-length */ - } + @Test + fun load_nonexistentResource_shouldThrowResourceNotFoundException() { + val resourceNotFoundException = + assertThrows(ResourceNotFoundException::class.java) { + fhirEngine.load(Patient::class.java, "nonexistent_patient") + } + /* ktlint-disable max-line-length */ + Truth.assertThat(resourceNotFoundException.message) + .isEqualTo( + "Resource not found with type ${ResourceType.Patient.name} and id nonexistent_patient!" + ) + /* ktlint-enable max-line-length */ + } - @Test - fun load_shouldReturnResource() { - testingUtils.assertResourceEquals( - TEST_PATIENT_1, - fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) - ) - } + @Test + fun load_shouldReturnResource() { + testingUtils.assertResourceEquals( + TEST_PATIENT_1, + fhirEngine.load(Patient::class.java, TEST_PATIENT_1_ID) + ) + } - companion object { - private const val TEST_PATIENT_1_ID = "test_patient_1" - private var TEST_PATIENT_1 = Patient() - init { - TEST_PATIENT_1.setId(TEST_PATIENT_1_ID) - TEST_PATIENT_1.setGender(Enumerations.AdministrativeGender.MALE) - } + companion object { + private const val TEST_PATIENT_1_ID = "test_patient_1" + private var TEST_PATIENT_1 = Patient() + init { + TEST_PATIENT_1.setId(TEST_PATIENT_1_ID) + TEST_PATIENT_1.setGender(Enumerations.AdministrativeGender.MALE) + } - private const val TEST_PATIENT_2_ID = "test_patient_2" - private var TEST_PATIENT_2 = Patient() - init { - TEST_PATIENT_2.setId(TEST_PATIENT_2_ID) - TEST_PATIENT_2.setGender(Enumerations.AdministrativeGender.MALE) - } + private const val TEST_PATIENT_2_ID = "test_patient_2" + private var TEST_PATIENT_2 = Patient() + init { + TEST_PATIENT_2.setId(TEST_PATIENT_2_ID) + TEST_PATIENT_2.setGender(Enumerations.AdministrativeGender.MALE) } + } } diff --git a/core/src/test/java/com/google/android/fhir/index/impl/ResourceIndexerTest.kt b/core/src/test/java/com/google/android/fhir/index/impl/ResourceIndexerTest.kt index 934fe40ccf..75f8bb017a 100644 --- a/core/src/test/java/com/google/android/fhir/index/impl/ResourceIndexerTest.kt +++ b/core/src/test/java/com/google/android/fhir/index/impl/ResourceIndexerTest.kt @@ -54,389 +54,384 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class ResourceIndexerTest { - private lateinit var qtyTestSubstance: Substance - private lateinit var qtyTestInvoice: Invoice - private lateinit var uriTestQuestionnaire: Questionnaire - private lateinit var dateTestPatient: Patient - private lateinit var lastUpdatedTestPatient: Patient - private lateinit var numberTestChargeItem: ChargeItem - private lateinit var numberTestMolecularSequence: MolecularSequence - - @Before - fun setUp() { - val testingUtils = TestingUtils(FhirContext.forR4().newJsonParser()) - // TODO: Improve sample data reading. Current approach has a downside of failing all tests if - // one file name is mistyped. - qtyTestSubstance = - testingUtils.readFromFile(Substance::class.java, "/quantity_test_substance.json") - qtyTestInvoice = - testingUtils.readFromFile(Invoice::class.java, "/quantity_test_invoice.json") - uriTestQuestionnaire = - testingUtils.readFromFile(Questionnaire::class.java, "/uri_test_questionnaire.json") - dateTestPatient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") - lastUpdatedTestPatient = - testingUtils.readFromFile(Patient::class.java, "/lastupdated_ts_test_patient.json") - numberTestChargeItem = - testingUtils.readFromFile(ChargeItem::class.java, "/number_test_charge_item.json") - numberTestMolecularSequence = - testingUtils.readFromFile(MolecularSequence::class.java, - "/number_test_molecular_sequence.json") - } - - @Test - fun index_patient_shouldIndexGivenName() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_1) - assertThat(resourceIndices.stringIndices) - .contains( - StringIndex("given", "Patient.name.given", TEST_PATIENT_1_GIVEN_NAME_1) - ) - } - - @Test - fun index_patient_shouldIndexManagingOrganization() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_1) - assertThat(resourceIndices.referenceIndices) - .contains( - ReferenceIndex("organization", "Patient.managingOrganization", TEST_PATIENT_1_ORG) - ) - } - - @Test - fun index_observation_shouldIndexSubject() { - val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_1) - assertThat(resourceIndices.referenceIndices) - .contains( - ReferenceIndex("subject", "Observation.subject", "Patient/" + TEST_PATIENT_1_ID) - ) - } - - @Test - fun index_observation_shouldIndexCode() { - val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_1) - assertThat(resourceIndices.tokenIndices) - .contains( - TokenIndex("code", "Observation.code", TEST_CODE_SYSTEM_1, TEST_CODE_VALUE_1) - ) - } - - @Test - fun index_patient_nullGivenName_shouldNotIndexGivenName() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.path.equals("Patient.name.given") - } - ).isFalse() - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.name.equals("given") - } - ).isFalse() - } - - @Test - fun index_patient_nullOrganisation_shouldNotIndexOrganisation() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) - assertThat( - resourceIndices.referenceIndices.any { referenceIndex -> - referenceIndex.path.equals("Patient.managingOrganization") - } - ).isFalse() - assertThat( - resourceIndices.referenceIndices.any { referenceIndex -> - referenceIndex.name.equals("organization") - } + private lateinit var qtyTestSubstance: Substance + private lateinit var qtyTestInvoice: Invoice + private lateinit var uriTestQuestionnaire: Questionnaire + private lateinit var dateTestPatient: Patient + private lateinit var lastUpdatedTestPatient: Patient + private lateinit var numberTestChargeItem: ChargeItem + private lateinit var numberTestMolecularSequence: MolecularSequence + + @Before + fun setUp() { + val testingUtils = TestingUtils(FhirContext.forR4().newJsonParser()) + // TODO: Improve sample data reading. Current approach has a downside of failing all tests + // if + // one file name is mistyped. + qtyTestSubstance = + testingUtils.readFromFile(Substance::class.java, "/quantity_test_substance.json") + qtyTestInvoice = testingUtils.readFromFile(Invoice::class.java, "/quantity_test_invoice.json") + uriTestQuestionnaire = + testingUtils.readFromFile(Questionnaire::class.java, "/uri_test_questionnaire.json") + dateTestPatient = testingUtils.readFromFile(Patient::class.java, "/date_test_patient.json") + lastUpdatedTestPatient = + testingUtils.readFromFile(Patient::class.java, "/lastupdated_ts_test_patient.json") + numberTestChargeItem = + testingUtils.readFromFile(ChargeItem::class.java, "/number_test_charge_item.json") + numberTestMolecularSequence = + testingUtils.readFromFile( + MolecularSequence::class.java, + "/number_test_molecular_sequence.json" + ) + } + + @Test + fun index_patient_shouldIndexGivenName() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_1) + assertThat(resourceIndices.stringIndices) + .contains(StringIndex("given", "Patient.name.given", TEST_PATIENT_1_GIVEN_NAME_1)) + } + + @Test + fun index_patient_shouldIndexManagingOrganization() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_1) + assertThat(resourceIndices.referenceIndices) + .contains(ReferenceIndex("organization", "Patient.managingOrganization", TEST_PATIENT_1_ORG)) + } + + @Test + fun index_observation_shouldIndexSubject() { + val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_1) + assertThat(resourceIndices.referenceIndices) + .contains(ReferenceIndex("subject", "Observation.subject", "Patient/" + TEST_PATIENT_1_ID)) + } + + @Test + fun index_observation_shouldIndexCode() { + val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_1) + assertThat(resourceIndices.tokenIndices) + .contains(TokenIndex("code", "Observation.code", TEST_CODE_SYSTEM_1, TEST_CODE_VALUE_1)) + } + + @Test + fun index_patient_nullGivenName_shouldNotIndexGivenName() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) + assertThat( + resourceIndices.stringIndices.any { stringIndex -> + stringIndex.path.equals("Patient.name.given") + } + ) + .isFalse() + assertThat( + resourceIndices.stringIndices.any { stringIndex -> stringIndex.name.equals("given") } + ) + .isFalse() + } + + @Test + fun index_patient_nullOrganisation_shouldNotIndexOrganisation() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) + assertThat( + resourceIndices.referenceIndices.any { referenceIndex -> + referenceIndex.path.equals("Patient.managingOrganization") + } + ) + .isFalse() + assertThat( + resourceIndices.referenceIndices.any { referenceIndex -> + referenceIndex.name.equals("organization") + } + ) + } + + @Test + fun index_patient_emptyGivenName_shouldNotIndexGivenName() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) + assertThat( + resourceIndices.stringIndices.any { stringIndex -> + stringIndex.path.equals("Patient.name.given") + } + ) + .isFalse() + assertThat( + resourceIndices.stringIndices.any { stringIndex -> stringIndex.name.equals("given") } + ) + } + + @Test + fun index_patient_emptyOrganisation_shouldNotIndexOrganisation() { + val resourceIndices = ResourceIndexer.index(TEST_PATIENT_EMPTY_FIELDS) + assertThat( + resourceIndices.referenceIndices.any { referenceIndex -> + referenceIndex.path.equals("Patient.managingOrganization") + } + ) + .isFalse() + assertThat( + resourceIndices.referenceIndices.any { referenceIndex -> + referenceIndex.name.equals("organization") + } + ) + .isFalse() + } + + @Test + fun index_observation_nullCode_shouldNotIndexCode() { + val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_NULL_CODE) + assertThat( + resourceIndices.stringIndices.any { stringIndex -> + stringIndex.path.equals("Observation.code") + } + ) + .isFalse() + assertThat(resourceIndices.stringIndices.any { stringIndex -> stringIndex.name.equals("code") }) + .isFalse() + } + + @Test + fun index_observation_emptyCode_shouldNotIndexCode() { + val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_EMPTY_CODE) + assertThat( + resourceIndices.stringIndices.any { stringIndex -> + stringIndex.path.equals("Observation.code") + } + ) + .isFalse() + assertThat(resourceIndices.stringIndices.any { stringIndex -> stringIndex.name.equals("code") }) + .isFalse() + } + + @Test + fun index_invoice_shouldIndexMoneyQuantity() { + val resourceIndices = ResourceIndexer.index(qtyTestInvoice) + assertThat(resourceIndices.quantityIndices) + .containsAtLeast( + // Search parameter names flatten camel case so "totalGross" becomes "totalgross" + QuantityIndex( + "totalgross", + "Invoice.totalGross", + FHIR_CURRENCY_SYSTEM, + "EUR", + BigDecimal("48") + ), + QuantityIndex( + "totalnet", + "Invoice.totalNet", + FHIR_CURRENCY_SYSTEM, + "EUR", + BigDecimal("40.22") ) - } - - @Test - fun index_patient_emptyGivenName_shouldNotIndexGivenName() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_NULL_FIELDS) - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.path.equals("Patient.name.given") - } - ).isFalse() - assertThat( - resourceIndices.stringIndices.any { stringIndex -> stringIndex.name.equals("given") } + ) + } + + @Test + fun index_substance_shouldIndexQuantityQuantity() { + val resourceIndices = ResourceIndexer.index(qtyTestSubstance) + assertThat(resourceIndices.quantityIndices) + .contains( + QuantityIndex( + "quantity", + "Substance.instance.quantity", + "http://unitsofmeasure.org", + "mL", + BigDecimal("100") ) + ) + } + + @Test + fun index_questionnaire_shouldIndexUri() { + val resourceIndices = ResourceIndexer.index(uriTestQuestionnaire) + assertThat(resourceIndices.uriIndices) + .contains(UriIndex("url", "Questionnaire.url", "http://hl7.org/fhir/Questionnaire/3141")) + } + + @Test + fun index_patient_birthDate_shouldIndexBirthDate() { + val resourceIndices = ResourceIndexer.index(dateTestPatient) + val birthDateElement = dateTestPatient.getBirthDateElement() + assertThat(resourceIndices.dateIndices) + .contains( + DateIndex( + "birthdate", + "Patient.birthDate", + birthDateElement.getValue().getTime(), + birthDateElement.getValue().getTime(), + birthDateElement.getPrecision() + ) + ) + } + + @Test + fun index_patient_lastUpdated_shouldIndexLastUpdated() { + val resourceIndices = ResourceIndexer.index(lastUpdatedTestPatient) + val lastUpdatedElement = lastUpdatedTestPatient.getMeta().getLastUpdatedElement() + assertThat(resourceIndices.dateIndices) + .contains( + DateIndex( + "lastUpdated", + "Patient.meta.lastUpdated", + lastUpdatedElement.getValue().getTime(), + lastUpdatedElement.getValue().getTime(), + lastUpdatedElement.getPrecision() + ) + ) + } + + @Test + fun index_chargeItem_shouldIndexFactorOverride() { + val resourceIndices = ResourceIndexer.index(numberTestChargeItem) + assertThat(resourceIndices.numberIndices) + .contains(NumberIndex("factor-override", "ChargeItem.factorOverride", BigDecimal("0.8"))) + } + + @Test + fun index_molecularSequence_shouldIndexWindowAndVariant() { + val resourceIndices = ResourceIndexer.index(numberTestMolecularSequence) + assertThat(resourceIndices.numberIndices) + .containsAtLeast( + NumberIndex( + "window-end", + "MolecularSequence.referenceSeq.windowEnd", + BigDecimal("22125510") + ), + NumberIndex( + "window-start", + "MolecularSequence.referenceSeq.windowStart", + BigDecimal("22125500") + ), + NumberIndex("variant-end", "MolecularSequence.variant.end", BigDecimal("22125504")), + NumberIndex("variant-start", "MolecularSequence.variant.start", BigDecimal("22125503")) + ) + } + + // TODO: improve the tests further. + + private companion object { + const val TEST_CODE_SYSTEM_1 = "http://openmrs.org/concepts" + const val TEST_CODE_VALUE_1 = "1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + val TEST_PATIENT_NULL_ID = Patient() + + init { + TEST_PATIENT_NULL_ID.id = null } - @Test - fun index_patient_emptyOrganisation_shouldNotIndexOrganisation() { - val resourceIndices = ResourceIndexer.index(TEST_PATIENT_EMPTY_FIELDS) - assertThat( - resourceIndices.referenceIndices.any { referenceIndex -> - referenceIndex.path.equals("Patient.managingOrganization") - } - ).isFalse() - assertThat( - resourceIndices.referenceIndices.any { referenceIndex -> - referenceIndex.name.equals("organization") - } - ).isFalse() - } - - @Test - fun index_observation_nullCode_shouldNotIndexCode() { - val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_NULL_CODE) - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.path.equals("Observation.code") - } - ).isFalse() - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.name.equals("code") - } - ).isFalse() - } - - @Test - fun index_observation_emptyCode_shouldNotIndexCode() { - val resourceIndices = ResourceIndexer.index(TEST_OBSERVATION_EMPTY_CODE) - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.path.equals("Observation.code") - } - ).isFalse() - assertThat( - resourceIndices.stringIndices.any { stringIndex -> - stringIndex.name.equals("code") - } - ).isFalse() - } - - @Test - fun index_invoice_shouldIndexMoneyQuantity() { - val resourceIndices = ResourceIndexer.index(qtyTestInvoice) - assertThat(resourceIndices.quantityIndices) - .containsAtLeast( - // Search parameter names flatten camel case so "totalGross" becomes "totalgross" - QuantityIndex( - "totalgross", - "Invoice.totalGross", - FHIR_CURRENCY_SYSTEM, - "EUR", - BigDecimal("48") - ), - QuantityIndex( - "totalnet", - "Invoice.totalNet", - FHIR_CURRENCY_SYSTEM, - "EUR", - BigDecimal("40.22") + // TEST_PATIENT_1 loosely based on https://www.hl7.org/fhir/patient-example-b.json.html + const val TEST_PATIENT_1_ID = "Patient/pat2" + const val TEST_PATIENT_1_IDENTIFIER_SYSTEM = "urn:oid:0.1.2.3.4.5.6.7" + const val TEST_PATIENT_1_IDENTIFIER_VALUE = "123456" + const val TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_CODE = "MR" + const val TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_SYSTEM = + "http://terminology.hl7.org/CodeSystem/v2-0203" + const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_CODE = "A" + const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_SYSTEM = + "http://terminology.hl7.org/CodeSystem/v2-0001" + const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_DISPLAY = "Ambiguous" + const val TEST_PATIENT_1_EMAIL = "donald_duck@disney.com" + const val TEST_PATIENT_1_PHONE = "+1-877-764-2539" + const val TEST_PATIENT_1_GIVEN_NAME_1 = "Donald" + const val TEST_PATIENT_1_GIVEN_NAME_2 = "D" + const val TEST_PATIENT_1_FAMILY = "Duck" + const val TEST_PATIENT_1_ORG = "Organization/1" + + val TEST_PATIENT_1 = Patient() + + init { + TEST_PATIENT_1.id = TEST_PATIENT_1_ID + TEST_PATIENT_1.addIdentifier( + Identifier() + .setUse(Identifier.IdentifierUse.USUAL) + .setType( + CodeableConcept() + .addCoding( + Coding() + .setCode(TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_CODE) + .setSystem(TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_SYSTEM) + ) + ) + .setSystem(TEST_PATIENT_1_IDENTIFIER_SYSTEM) + .setValue(TEST_PATIENT_1_IDENTIFIER_VALUE) + ) + TEST_PATIENT_1.active = true + TEST_PATIENT_1.addName( + HumanName() + .addGiven(TEST_PATIENT_1_GIVEN_NAME_1) + .addGiven(TEST_PATIENT_1_GIVEN_NAME_2) + .setFamily(TEST_PATIENT_1_FAMILY) + ) + TEST_PATIENT_1.gender = Enumerations.AdministrativeGender.OTHER + TEST_PATIENT_1 + .getGenderElement() + .addExtension( + Extension() + .setValue( + CodeableConcept() + .addCoding( + Coding() + .setCode(TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_CODE) + .setSystem(TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_SYSTEM) + .setDisplay(TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_DISPLAY) ) ) + ) + TEST_PATIENT_1.addTelecom( + ContactPoint() + .setSystem(ContactPoint.ContactPointSystem.EMAIL) + .setValue(TEST_PATIENT_1_EMAIL) + ) + TEST_PATIENT_1.addTelecom( + ContactPoint() + .setSystem(ContactPoint.ContactPointSystem.PHONE) + .setValue(TEST_PATIENT_1_PHONE) + ) + TEST_PATIENT_1.managingOrganization = Reference().setReference(TEST_PATIENT_1_ORG) } - @Test - fun index_substance_shouldIndexQuantityQuantity() { - val resourceIndices = ResourceIndexer.index(qtyTestSubstance) - assertThat(resourceIndices.quantityIndices) - .contains( - QuantityIndex( - "quantity", - "Substance.instance.quantity", - "http://unitsofmeasure.org", - "mL", - BigDecimal("100") - ) - ) - } + val TEST_PATIENT_NULL_FIELDS = Patient() - @Test - fun index_questionnaire_shouldIndexUri() { - val resourceIndices = ResourceIndexer.index(uriTestQuestionnaire) - assertThat(resourceIndices.uriIndices) - .contains( - UriIndex("url", "Questionnaire.url", "http://hl7.org/fhir/Questionnaire/3141") - ) + init { + TEST_PATIENT_NULL_FIELDS.id = "non_null_id" + TEST_PATIENT_NULL_FIELDS.addName(HumanName().addGiven(null)) + TEST_PATIENT_NULL_FIELDS.managingOrganization = Reference().setReference(null) } - @Test - fun index_patient_birthDate_shouldIndexBirthDate() { - val resourceIndices = ResourceIndexer.index(dateTestPatient) - val birthDateElement = dateTestPatient.getBirthDateElement() - assertThat(resourceIndices.dateIndices) - .contains( - DateIndex( - "birthdate", - "Patient.birthDate", - birthDateElement.getValue().getTime(), - birthDateElement.getValue().getTime(), - birthDateElement.getPrecision() - ) - ) - } + val TEST_PATIENT_EMPTY_FIELDS = Patient() - @Test - fun index_patient_lastUpdated_shouldIndexLastUpdated() { - val resourceIndices = ResourceIndexer.index(lastUpdatedTestPatient) - val lastUpdatedElement = lastUpdatedTestPatient.getMeta().getLastUpdatedElement() - assertThat(resourceIndices.dateIndices) - .contains( - DateIndex( - "lastUpdated", - "Patient.meta.lastUpdated", - lastUpdatedElement.getValue().getTime(), - lastUpdatedElement.getValue().getTime(), - lastUpdatedElement.getPrecision() - ) - ) + init { + TEST_PATIENT_EMPTY_FIELDS.id = "anonymous_patient" + TEST_PATIENT_EMPTY_FIELDS.addName(HumanName().addGiven("")) + TEST_PATIENT_EMPTY_FIELDS.managingOrganization = Reference().setReference("") } - @Test - fun index_chargeItem_shouldIndexFactorOverride() { - val resourceIndices = ResourceIndexer.index(numberTestChargeItem) - assertThat(resourceIndices.numberIndices) - .contains( - NumberIndex("factor-override", "ChargeItem.factorOverride", BigDecimal("0.8")) - ) - } + const val TEST_OBSERVATION_1_ID = "test_observation_1" + val TEST_OBSERVATION_1 = Observation() - @Test - fun index_molecularSequence_shouldIndexWindowAndVariant() { - val resourceIndices = ResourceIndexer.index(numberTestMolecularSequence) - assertThat(resourceIndices.numberIndices) - .containsAtLeast( - NumberIndex( - "window-end", - "MolecularSequence.referenceSeq.windowEnd", - BigDecimal("22125510") - ), - NumberIndex( - "window-start", - "MolecularSequence.referenceSeq.windowStart", - BigDecimal("22125500") - ), - NumberIndex( - "variant-end", "MolecularSequence.variant.end", BigDecimal("22125504") - ), - NumberIndex( - "variant-start", "MolecularSequence.variant.start", BigDecimal("22125503") - ) - ) + init { + TEST_OBSERVATION_1.id = TEST_OBSERVATION_1_ID + TEST_OBSERVATION_1.subject = Reference().setReference("Patient/" + TEST_PATIENT_1_ID) + TEST_OBSERVATION_1.code = + CodeableConcept() + .addCoding(Coding().setSystem(TEST_CODE_SYSTEM_1).setCode(TEST_CODE_VALUE_1)) } - // TODO: improve the tests further. - - private companion object { - const val TEST_CODE_SYSTEM_1 = "http://openmrs.org/concepts" - const val TEST_CODE_VALUE_1 = "1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - - val TEST_PATIENT_NULL_ID = Patient() - - init { - TEST_PATIENT_NULL_ID.id = null - } - - // TEST_PATIENT_1 loosely based on https://www.hl7.org/fhir/patient-example-b.json.html - const val TEST_PATIENT_1_ID = "Patient/pat2" - const val TEST_PATIENT_1_IDENTIFIER_SYSTEM = "urn:oid:0.1.2.3.4.5.6.7" - const val TEST_PATIENT_1_IDENTIFIER_VALUE = "123456" - const val TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_CODE = "MR" - const val TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_SYSTEM = - "http://terminology.hl7.org/CodeSystem/v2-0203" - const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_CODE = "A" - const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_SYSTEM = - "http://terminology.hl7.org/CodeSystem/v2-0001" - const val TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_DISPLAY = "Ambiguous" - const val TEST_PATIENT_1_EMAIL = "donald_duck@disney.com" - const val TEST_PATIENT_1_PHONE = "+1-877-764-2539" - const val TEST_PATIENT_1_GIVEN_NAME_1 = "Donald" - const val TEST_PATIENT_1_GIVEN_NAME_2 = "D" - const val TEST_PATIENT_1_FAMILY = "Duck" - const val TEST_PATIENT_1_ORG = "Organization/1" - - val TEST_PATIENT_1 = Patient() - - init { - TEST_PATIENT_1.id = TEST_PATIENT_1_ID - TEST_PATIENT_1.addIdentifier( - Identifier() - .setUse(Identifier.IdentifierUse.USUAL) - .setType( - CodeableConcept() - .addCoding( - Coding() - .setCode(TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_CODE) - .setSystem(TEST_PATIENT_1_IDENTIFIER_TYPE_CODING_SYSTEM))) - .setSystem(TEST_PATIENT_1_IDENTIFIER_SYSTEM) - .setValue(TEST_PATIENT_1_IDENTIFIER_VALUE)) - TEST_PATIENT_1.active = true - TEST_PATIENT_1.addName( - HumanName() - .addGiven(TEST_PATIENT_1_GIVEN_NAME_1) - .addGiven(TEST_PATIENT_1_GIVEN_NAME_2) - .setFamily(TEST_PATIENT_1_FAMILY)) - TEST_PATIENT_1.gender = Enumerations.AdministrativeGender.OTHER - TEST_PATIENT_1 - .getGenderElement() - .addExtension( - Extension() - .setValue( - CodeableConcept() - .addCoding( - Coding() - .setCode(TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_CODE) - .setSystem(TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_SYSTEM) - .setDisplay( - TEST_PATIENT_1_GENDER_EXT_VALUE_CODING_DISPLAY)))) - TEST_PATIENT_1.addTelecom( - ContactPoint() - .setSystem(ContactPoint.ContactPointSystem.EMAIL) - .setValue(TEST_PATIENT_1_EMAIL)) - TEST_PATIENT_1.addTelecom( - ContactPoint() - .setSystem(ContactPoint.ContactPointSystem.PHONE) - .setValue(TEST_PATIENT_1_PHONE)) - TEST_PATIENT_1.managingOrganization = Reference().setReference(TEST_PATIENT_1_ORG) - } - - val TEST_PATIENT_NULL_FIELDS = Patient() - - init { - TEST_PATIENT_NULL_FIELDS.id = "non_null_id" - TEST_PATIENT_NULL_FIELDS.addName(HumanName().addGiven(null)) - TEST_PATIENT_NULL_FIELDS.managingOrganization = Reference().setReference(null) - } - - val TEST_PATIENT_EMPTY_FIELDS = Patient() - - init { - TEST_PATIENT_EMPTY_FIELDS.id = "anonymous_patient" - TEST_PATIENT_EMPTY_FIELDS.addName(HumanName().addGiven("")) - TEST_PATIENT_EMPTY_FIELDS.managingOrganization = Reference().setReference("") - } - - const val TEST_OBSERVATION_1_ID = "test_observation_1" - val TEST_OBSERVATION_1 = Observation() + val TEST_OBSERVATION_NULL_CODE = Observation() - init { - TEST_OBSERVATION_1.id = TEST_OBSERVATION_1_ID - TEST_OBSERVATION_1.subject = Reference().setReference("Patient/" + TEST_PATIENT_1_ID) - TEST_OBSERVATION_1.code = CodeableConcept().addCoding( - Coding().setSystem(TEST_CODE_SYSTEM_1).setCode(TEST_CODE_VALUE_1)) - } - - val TEST_OBSERVATION_NULL_CODE = Observation() - - init { - TEST_OBSERVATION_NULL_CODE.id = "non_null_id" - TEST_OBSERVATION_NULL_CODE.code = - CodeableConcept().addCoding(Coding().setSystem(null).setCode(null)) - } - - val TEST_OBSERVATION_EMPTY_CODE = Observation() + init { + TEST_OBSERVATION_NULL_CODE.id = "non_null_id" + TEST_OBSERVATION_NULL_CODE.code = + CodeableConcept().addCoding(Coding().setSystem(null).setCode(null)) + } - init { - TEST_OBSERVATION_EMPTY_CODE.id = "non_empty_id" - TEST_OBSERVATION_EMPTY_CODE.code = - CodeableConcept().addCoding(Coding().setSystem("").setCode("")) - } + val TEST_OBSERVATION_EMPTY_CODE = Observation() - // See: https://www.hl7.org/fhir/valueset-currencies.html - const val FHIR_CURRENCY_SYSTEM = "urn:iso:std:iso:4217" + init { + TEST_OBSERVATION_EMPTY_CODE.id = "non_empty_id" + TEST_OBSERVATION_EMPTY_CODE.code = + CodeableConcept().addCoding(Coding().setSystem("").setCode("")) } + + // See: https://www.hl7.org/fhir/valueset-currencies.html + const val FHIR_CURRENCY_SYSTEM = "urn:iso:std:iso:4217" + } } diff --git a/core/src/test/java/com/google/android/fhir/resource/ResourcesTest.kt b/core/src/test/java/com/google/android/fhir/resource/ResourcesTest.kt index 316778d233..04edd3d470 100644 --- a/core/src/test/java/com/google/android/fhir/resource/ResourcesTest.kt +++ b/core/src/test/java/com/google/android/fhir/resource/ResourcesTest.kt @@ -29,13 +29,13 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class ResourcesTest { - @Test - fun getResourceType() { - assertThat(getResourceType(Patient::class.java)).isEqualTo(ResourceType.Patient) - } + @Test + fun getResourceType() { + assertThat(getResourceType(Patient::class.java)).isEqualTo(ResourceType.Patient) + } - @Test - fun getResourceClass() { - assertThat(getResourceClass("Patient")).isEqualTo(Patient::class.java) - } + @Test + fun getResourceClass() { + assertThat(getResourceClass("Patient")).isEqualTo(Patient::class.java) + } } diff --git a/core/src/test/java/com/google/android/fhir/search/impl/SerializedResourceQueryTest.kt b/core/src/test/java/com/google/android/fhir/search/impl/SerializedResourceQueryTest.kt index 9132359288..65188dc684 100644 --- a/core/src/test/java/com/google/android/fhir/search/impl/SerializedResourceQueryTest.kt +++ b/core/src/test/java/com/google/android/fhir/search/impl/SerializedResourceQueryTest.kt @@ -29,111 +29,114 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class SerializedResourceQueryTest { - @Test - fun getQueryString_noResourceIdQuery_noSortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, null, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + @Test + fun getQueryString_noResourceIdQuery_noSortCriterion_noLimit_noSkip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, null, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_noSortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, null, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo(listOf(RESOURCE_TYPE.name)) - } - - @Test - fun getQueryString_noResourceIdQuery_noSortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_noSortCriterion_noLimit_noSkip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, null, null) + assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo(listOf(RESOURCE_TYPE.name)) + } + + @Test + fun getQueryString_noResourceIdQuery_noSortCriterion_limit_noSkip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_noSortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo(listOf(RESOURCE_TYPE.name, 10)) - } - - @Test - fun getQueryString_noResourceIdQuery_noSortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_noSortCriterion_limit_noSkip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, null) + assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo(listOf(RESOURCE_TYPE.name, 10)) + } + + @Test + fun getQueryString_noResourceIdQuery_noSortCriterion_limit_skip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_noSortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, 20) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf(RESOURCE_TYPE.name, 10, 20)) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_ascending_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_noSortCriterion_limit_skip() { + val serializedResourceQuery = SerializedResourceQuery(RESOURCE_TYPE, null, null, 10, 20) + assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo(listOf(RESOURCE_TYPE.name, 10, 20)) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_ascending_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, null, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b ON a.resourceType = b.resourceType AND a.resourceId = b.resourceId AND b.index_name = ? WHERE a.resourceType = ? ORDER BY b.index_value ASC - """.trimIndent()) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_descending_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_descending_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, null, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b ON a.resourceType = b.resourceType AND a.resourceId = b.resourceId AND b.index_name = ? WHERE a.resourceType = ? ORDER BY b.index_value DESC - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_sortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name)) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_ascending_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_sortCriterion_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, null, null) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name)) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_ascending_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -141,15 +144,17 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? ORDER BY b.index_value ASC LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_descending_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_descending_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -157,24 +162,25 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? ORDER BY b.index_value DESC LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_sortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, 10)) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_ascending_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_sortCriterion_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, 10)) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_ascending_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -182,15 +188,17 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? ORDER BY b.index_value ASC LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryString_noResourceIdQuery_sortCriterion_descending_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_noResourceIdQuery_sortCriterion_descending_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_DESCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -198,125 +206,153 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? ORDER BY b.index_value DESC LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_noResourceIdQuery_sortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, 10, 20)) - } - - @Test - fun getQueryString_resourceIdQuery_noSortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, null, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_noResourceIdQuery_sortCriterion_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, null, SORT_CRITERION_ASCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, 10, 20)) + } + + @Test + fun getQueryString_resourceIdQuery_noSortCriterion_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, null, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_noSortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, null, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]")) - } - - @Test - fun getQueryString_resourceIdQuery_noSortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_noSortCriterion_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, null, null) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]")) + } + + @Test + fun getQueryString_resourceIdQuery_noSortCriterion_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_noSortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10)) - } - - @Test - fun getQueryString_resourceIdQuery_noSortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_noSortCriterion_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, null) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10)) + } + + @Test + fun getQueryString_resourceIdQuery_noSortCriterion_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_noSortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, 20) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10, 20)) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_ascending_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_noSortCriterion_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, null, 10, 20) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf(RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10, 20)) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_ascending_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery( + RESOURCE_TYPE, + RESOURCE_ID_QUERY, + SORT_CRITERION_ASCENDING, + null, + null + ) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b ON a.resourceType = b.resourceType AND a.resourceId = b.resourceId AND b.index_name = ? WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value ASC - """.trimIndent()) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_descending_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_DESCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_descending_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery( + RESOURCE_TYPE, + RESOURCE_ID_QUERY, + SORT_CRITERION_DESCENDING, + null, + null + ) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b ON a.resourceType = b.resourceType AND a.resourceId = b.resourceId AND b.index_name = ? WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value DESC - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_sortCriterion_noLimit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - null, - null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]")) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_ascending_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_sortCriterion_noLimit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery( + RESOURCE_TYPE, + RESOURCE_ID_QUERY, + SORT_CRITERION_ASCENDING, + null, + null + ) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]")) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_ascending_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -324,15 +360,17 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value ASC LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_descending_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_DESCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_descending_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_DESCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -340,24 +378,25 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value DESC LIMIT ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_sortCriterion_limit_noSkip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - 10, null) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10)) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_ascending_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_sortCriterion_limit_noSkip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, 10, null) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10)) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_ascending_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -365,15 +404,17 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value ASC LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryString_resourceIdQuery_sortCriterion_descending_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_DESCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryString()).isEqualTo(""" + """.trimIndent() + ) + } + + @Test + fun getQueryString_resourceIdQuery_sortCriterion_descending_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_DESCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryString()) + .isEqualTo( + """ SELECT a.serializedResource FROM ResourceEntity a LEFT JOIN [[SORT_TABLE]] b @@ -381,31 +422,33 @@ class SerializedResourceQueryTest { WHERE a.resourceType = ? AND a.resourceId IN ([[RESOURCE_ID_QUERY]]) ORDER BY b.index_value DESC LIMIT ? OFFSET ? - """.trimIndent()) - } - - @Test - fun getQueryArgs_resourceIdQuery_sortCriterion_limit_skip() { - val serializedResourceQuery = - SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, - 10, 20) - assertThat(serializedResourceQuery.getQueryArgs()).isEqualTo( - listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10, 20)) - } - - companion object { - val RESOURCE_TYPE = ResourceType.Patient - val RESOURCE_ID_QUERY = - ResourceIdQuery("[[RESOURCE_ID_QUERY]]", listOf("[[RESOURCE_ID_QUERY_ARG]]")) - val SORT_CRITERION_ASCENDING = object : SortCriterion { - override val table = "[[SORT_TABLE]]" - override val param = "[[SORT_PARAM]]" - override val ascending = true - } - val SORT_CRITERION_DESCENDING = object : SortCriterion { - override val table = "[[SORT_TABLE]]" - override val param = "[[SORT_PARAM]]" - override val ascending = false - } - } + """.trimIndent() + ) + } + + @Test + fun getQueryArgs_resourceIdQuery_sortCriterion_limit_skip() { + val serializedResourceQuery = + SerializedResourceQuery(RESOURCE_TYPE, RESOURCE_ID_QUERY, SORT_CRITERION_ASCENDING, 10, 20) + assertThat(serializedResourceQuery.getQueryArgs()) + .isEqualTo(listOf("[[SORT_PARAM]]", RESOURCE_TYPE.name, "[[RESOURCE_ID_QUERY_ARG]]", 10, 20)) + } + + companion object { + val RESOURCE_TYPE = ResourceType.Patient + val RESOURCE_ID_QUERY = + ResourceIdQuery("[[RESOURCE_ID_QUERY]]", listOf("[[RESOURCE_ID_QUERY_ARG]]")) + val SORT_CRITERION_ASCENDING = + object : SortCriterion { + override val table = "[[SORT_TABLE]]" + override val param = "[[SORT_PARAM]]" + override val ascending = true + } + val SORT_CRITERION_DESCENDING = + object : SortCriterion { + override val table = "[[SORT_TABLE]]" + override val param = "[[SORT_PARAM]]" + override val ascending = false + } + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactoryInstrumentedTest.kt index 2fc9a31ee8..0b38ae1ced 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactoryInstrumentedTest.kt @@ -31,92 +31,108 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemCheckBoxViewHolderFactoryInstrumentedTest { - private val parent = FrameLayout(InstrumentationRegistry.getInstrumentation().context) - private val viewHolder = QuestionnaireItemCheckBoxViewHolderFactory.create(parent) + private val parent = FrameLayout(InstrumentationRegistry.getInstrumentation().context) + private val viewHolder = QuestionnaireItemCheckBoxViewHolderFactory.create(parent) - @Test - fun shouldSetCheckBoxText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetCheckBoxText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.check_box).text).isEqualTo( - "Question?" - ) - } + assertThat(viewHolder.itemView.findViewById(R.id.check_box).text) + .isEqualTo("Question?") + } - @Test - fun noAnswer_shouldSetCheckBoxUnchecked() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun noAnswer_shouldSetCheckBoxUnchecked() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isFalse() - } + assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isFalse() + } - @Test - @UiThreadTest - fun answerTrue_shouldSetCheckBoxChecked() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true)).build() - } - ) - ) {} - ) + @Test + @UiThreadTest + fun answerTrue_shouldSetCheckBoxChecked() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true)) + .build() + } + ) + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isTrue() - } + assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isTrue() + } - @Test - fun answerFalse_shouldSetCheckBoxUnchecked() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(false)).build() - } - ) - ) {} - ) + @Test + fun answerFalse_shouldSetCheckBoxUnchecked() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(false)) + .build() + } + ) + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isFalse() - } + assertThat(viewHolder.itemView.findViewById(R.id.check_box).isChecked).isFalse() + } - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemComponentAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.check_box).performClick() + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemComponentAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.check_box).performClick() - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.boolean.value).isTrue() - } + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.boolean.value).isTrue() + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryInstrumentedTest.kt index fd20bd256b..35e1f5dcee 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactoryInstrumentedTest.kt @@ -35,82 +35,89 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemDatePickerViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemDatePickerViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemDatePickerViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextInputLayoutHint() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextInputLayoutHint() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.question).text - ).isEqualTo("Question?") - } + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } - @Test - @UiThreadTest - fun shouldSetEmptyDateInput() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + @UiThreadTest + fun shouldSetEmptyDateInput() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() - ).isEqualTo("") - } + assertThat(viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString()) + .isEqualTo("") + } - @Test - @UiThreadTest - fun shouldSetDateInput() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setDate( - Date.newBuilder() - .setValueUs( - LocalDate - .of(2020, 1, 1) - .atStartOfDay() - .atZone(ZoneId.systemDefault()) - .toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND) - .setPrecision(Date.Precision.DAY) - .setTimezone(ZoneId.systemDefault().id) - ).build() - } - ) - ) {} - ) + @Test + @UiThreadTest + fun shouldSetDateInput() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setDate( + Date.newBuilder() + .setValueUs( + LocalDate.of(2020, 1, 1) + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + .toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND + ) + .setPrecision(Date.Precision.DAY) + .setTimezone(ZoneId.systemDefault().id) + ) + .build() + } + ) + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() - ).isEqualTo("2020-01-01") - } + assertThat(viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString()) + .isEqualTo("2020-01-01") + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryInstrumentedTest.kt index 62e2ff6aeb..3f565702bb 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactoryInstrumentedTest.kt @@ -36,92 +36,96 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemDateTimePickerViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - assertThat(parent).isNotNull() - viewHolder = QuestionnaireItemDateTimePickerViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + assertThat(parent).isNotNull() + viewHolder = QuestionnaireItemDateTimePickerViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextInputLayoutHint() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextInputLayoutHint() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.date_question).text - ).isEqualTo("Question?") - assertThat( - viewHolder.itemView.findViewById(R.id.time_question).text - ).isEqualTo("Question?") - } + assertThat(viewHolder.itemView.findViewById(R.id.date_question).text) + .isEqualTo("Question?") + assertThat(viewHolder.itemView.findViewById(R.id.time_question).text) + .isEqualTo("Question?") + } - @Test - @UiThreadTest - fun shouldSetEmptyDateTimeInput() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + @UiThreadTest + fun shouldSetEmptyDateTimeInput() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.dateInputEditText).text.toString() - ).isEqualTo("") - assertThat( - viewHolder.itemView.findViewById(R.id.timeInputEditText).text.toString() - ).isEqualTo("") - } + assertThat(viewHolder.itemView.findViewById(R.id.dateInputEditText).text.toString()) + .isEqualTo("") + assertThat(viewHolder.itemView.findViewById(R.id.timeInputEditText).text.toString()) + .isEqualTo("") + } - @Test - @UiThreadTest - fun shouldSetDateTimeInput() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setDateTime( - DateTime.newBuilder() - .setValueUs( - LocalDate - .of(2020, 1, 5) - .atTime(LocalTime.of(1, 30)) - .atZone(ZoneId.systemDefault()) - .toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND) - .setPrecision(DateTime.Precision.SECOND) - .setTimezone(ZoneId.systemDefault().id) - ).build() - } - ) - ) {} - ) + @Test + @UiThreadTest + fun shouldSetDateTimeInput() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setDateTime( + DateTime.newBuilder() + .setValueUs( + LocalDate.of(2020, 1, 5) + .atTime(LocalTime.of(1, 30)) + .atZone(ZoneId.systemDefault()) + .toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND + ) + .setPrecision(DateTime.Precision.SECOND) + .setTimezone(ZoneId.systemDefault().id) + ) + .build() + } + ) + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.dateInputEditText).text.toString() - ).isEqualTo("2020-01-05") - assertThat( - viewHolder.itemView.findViewById(R.id.timeInputEditText).text.toString() - ).isEqualTo("01:30:00") - } + assertThat(viewHolder.itemView.findViewById(R.id.dateInputEditText).text.toString()) + .isEqualTo("2020-01-05") + assertThat(viewHolder.itemView.findViewById(R.id.timeInputEditText).text.toString()) + .isEqualTo("01:30:00") + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt index 07574707c6..31504aa3f3 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest.kt @@ -32,67 +32,62 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemDisplayViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemDisplayViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemDisplayViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = - com.google.fhir.r4.core.String.newBuilder().setValue("Display").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { text = com.google.fhir.r4.core.String.newBuilder().setValue("Display").build() } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.text_view).text).isEqualTo( - "Display" - ) - } + assertThat(viewHolder.itemView.findViewById(R.id.text_view).text).isEqualTo("Display") + } - @Test - fun shouldSetTextViewVisible() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = - com.google.fhir.r4.core.String.newBuilder().setValue("Display").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewVisible() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { text = com.google.fhir.r4.core.String.newBuilder().setValue("Display").build() } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_view).visibility - ).isEqualTo(View.VISIBLE) - } + assertThat(viewHolder.itemView.findViewById(R.id.text_view).visibility) + .isEqualTo(View.VISIBLE) + } - @Test - fun shouldSetTextViewGone() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewGone() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { text = com.google.fhir.r4.core.String.newBuilder().setValue("").build() } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.text_view).visibility - ).isEqualTo(View.GONE) - } + assertThat(viewHolder.itemView.findViewById(R.id.text_view).visibility) + .isEqualTo(View.GONE) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryInstrumentedTest.kt index 2cf40efdb0..c62d0e1b66 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactoryInstrumentedTest.kt @@ -39,199 +39,179 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemDropDownViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemDropDownViewHolderFactory.create(parent) + } + + @Test + @UiThreadTest + fun shouldSetTextInputHint() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { text = String.newBuilder().setValue("Question?").build() } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat(viewHolder.itemView.findViewById(R.id.dropdown_question_title).text) + .isEqualTo("Question?") + } + + @Test + @UiThreadTest + fun shouldPopulateDropDown() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemDropDownViewHolderFactory.create(parent) - } - - @Test - @UiThreadTest - fun shouldSetTextInputHint() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + .build() + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().apply { addAnswerOption(answerOption) }.build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder + .itemView + .findViewById(R.id.auto_complete) + .adapter + .getItem(0) + .toString() + ) + .isEqualTo("Test Code") + } + + @Test + @UiThreadTest + fun shouldSetDropDownOptionToCodeIfValueCodingDisplayEmpty() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding(Coding.newBuilder().setCode(Code.newBuilder().setValue("test-code"))) ) - - assertThat( - viewHolder.itemView.findViewById(R.id.dropdown_question_title).text - ).isEqualTo("Question?") - } - - @Test - @UiThreadTest - fun shouldPopulateDropDown() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ) - ).build() - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAnswerOption(answerOption) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat(viewHolder.itemView.findViewById(R.id.auto_complete) - .adapter - .getItem(0) - .toString() - ).isEqualTo("Test Code") - } - - @Test - @UiThreadTest - fun shouldSetDropDownOptionToCodeIfValueCodingDisplayEmpty() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - ) - ).build() - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAnswerOption(answerOption) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + .build() + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().apply { addAnswerOption(answerOption) }.build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder + .itemView + .findViewById(R.id.auto_complete) + .adapter + .getItem(0) + .toString() + ) + .isEqualTo("test-code") + } + + @Test + @UiThreadTest + fun shouldSetAutoTextViewEmptyIfAnswerNull() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) ) - - assertThat( - viewHolder.itemView.findViewById(R.id.auto_complete) - .adapter - .getItem(0) - .toString() - ).isEqualTo("test-code") - } - - @Test - @UiThreadTest - fun shouldSetAutoTextViewEmptyIfAnswerNull() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ) - ).build() - - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAnswerOption(answerOption) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + .build() + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().apply { addAnswerOption(answerOption) }.build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.auto_complete).text.toString() + ) + .isEqualTo("") + } + + @Test + @UiThreadTest + fun shouldAutoCompleteTextViewToDisplayIfAnswerNotNull() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) ) - - assertThat( - viewHolder.itemView.findViewById(R.id.auto_complete) - .text - .toString() - ).isEqualTo("") - } - - @Test - @UiThreadTest - fun shouldAutoCompleteTextViewToDisplayIfAnswerNotNull() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ) - ).build() - - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAnswerOption(answerOption) - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder() - .setValue( - answerOption.responseAnswerValueX - ) - - ) - ) {} + .build() + + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().apply { addAnswerOption(answerOption) }.build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder() + .setValue(answerOption.responseAnswerValueX) + ) + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.auto_complete).text.toString() + ) + .isEqualTo(answerOption.displayString) + } + + @Test + @UiThreadTest + fun shouldThrowErrorForAnswerOptionWithoutCoding() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setStringValue(String.newBuilder().setValue("test")) ) - - assertThat( - viewHolder.itemView.findViewById(R.id.auto_complete) - .text - .toString() - ).isEqualTo(answerOption.displayString) - } - - @Test - @UiThreadTest - fun shouldThrowErrorForAnswerOptionWithoutCoding() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setStringValue( - String.newBuilder() - .setValue("test") - ) - ).build() - - assertFailsWith { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAnswerOption(answerOption) - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder() - .setValue( - answerOption.responseAnswerValueX - ) - - ) - ) {} + .build() + + assertFailsWith { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().apply { addAnswerOption(answerOption) }.build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder() + .setValue(answerOption.responseAnswerValueX) ) - } + ) {} + ) } + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactoryInstrumentedTest.kt index 36472eb1bc..99b517ae1d 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactoryInstrumentedTest.kt @@ -34,114 +34,119 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemEditTextDecimalViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemEditTextDecimalViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemEditTextDecimalViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.question).text) - .isEqualTo("Question?") - } + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } - @Test - @UiThreadTest - fun shouldSetInputText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setDecimal(Decimal.newBuilder().setValue("1.1")) - .build() - } - ) - ) {} - ) + @Test + @UiThreadTest + fun shouldSetInputText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setDecimal(Decimal.newBuilder().setValue("1.1")) + .build() + } + ) + ) {} + ) - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("1.1") - } + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("1.1") + } - @Test - @UiThreadTest - fun shouldSetInputTextToEmpty() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setDecimal(Decimal.newBuilder().setValue("1.1")) - .build() - } - ) - ) {} - ) - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + @UiThreadTest + fun shouldSetInputTextToEmpty() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setDecimal(Decimal.newBuilder().setValue("1.1")) + .build() + } + ) + ) {} + ) + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("") - } + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("") + } - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("1.1") + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("1.1") - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.decimal.value).isEqualTo("1.1") - } + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.decimal.value).isEqualTo("1.1") + } - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") - assertThat( - questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount - ).isEqualTo(0) - } + assertThat(questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount).isEqualTo(0) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactoryInstrumentedTest.kt index 5980302242..0ff1c86191 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactoryInstrumentedTest.kt @@ -34,114 +34,119 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemEditTextIntegerViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemEditTextIntegerViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemEditTextIntegerViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.question).text) - .isEqualTo("Question?") - } + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } - @Test - @UiThreadTest - fun shouldSetInputText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setInteger(Integer.newBuilder().setValue(5)) - .build() - } - ) - ) {} - ) + @Test + @UiThreadTest + fun shouldSetInputText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setInteger(Integer.newBuilder().setValue(5)) + .build() + } + ) + ) {} + ) - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("5") - } + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("5") + } - @Test - @UiThreadTest - fun shouldSetInputTextToEmpty() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setInteger(Integer.newBuilder().setValue(5)) - .build() - } - ) - ) {} - ) - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + @UiThreadTest + fun shouldSetInputTextToEmpty() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setInteger(Integer.newBuilder().setValue(5)) + .build() + } + ) + ) {} + ) + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("") - } + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("") + } - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10") + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10") - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.integer.value).isEqualTo(10) - } + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.integer.value).isEqualTo(10) + } - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") - assertThat( - questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount - ).isEqualTo(0) - } + assertThat(questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount).isEqualTo(0) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt index 77f9f5e424..6ca323edb5 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest.kt @@ -33,121 +33,125 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemEditTextMultiLineViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemEditTextMultiLineViewHolderFactory.create(parent) - } - - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat(viewHolder.itemView.findViewById(R.id.question).text) - .isEqualTo("Question?") - } - - @Test - @UiThreadTest - fun shouldSetInputText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setStringValue( - com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) - .build() - } - ) - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("Answer") - } - - @Test - @UiThreadTest - fun shouldSetInputTextToEmpty() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setStringValue( - com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) - .build() - } - ) - ) {} - ) - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText) - .setText("Answer") - - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.stringValue.value).isEqualTo("Answer") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") - - assertThat( - questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount - ).isEqualTo(0) - } + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemEditTextMultiLineViewHolderFactory.create(parent) + } + + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } + + @Test + @UiThreadTest + fun shouldSetInputText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setStringValue(com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) + .build() + } + ) + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("Answer") + } + + @Test + @UiThreadTest + fun shouldSetInputTextToEmpty() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setStringValue(com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) + .build() + } + ) + ) {} + ) + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("Answer") + + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.stringValue.value).isEqualTo("Answer") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") + + assertThat(questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount).isEqualTo(0) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryInstrumentedTest.kt index 24f59581db..8913ce1a42 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactoryInstrumentedTest.kt @@ -36,135 +36,140 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemEditTextQuantityViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemEditTextQuantityViewHolderFactory.create(parent) - } - - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat(viewHolder.itemView.findViewById(R.id.question).text) - .isEqualTo("Question?") - } - - @Test - @UiThreadTest - fun shouldSetInputText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setQuantity( - Quantity.newBuilder() - .setValue(Decimal.newBuilder().setValue("5").build()) - ).build() - } - ) - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("5") - } - - @Test - @UiThreadTest - fun shouldSetInputTextToEmpty() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setQuantity( - Quantity.newBuilder() - .setValue(Decimal.newBuilder().setValue("5").build()) - ).build() - } - ) - ) {} - ) - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10") - - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value!!.quantity!!.value!!.value).isEqualTo("10.0") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerOneDecimalPlace() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10.1") - - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value!!.quantity!!.value).isEqualTo( - Decimal.newBuilder().setValue("10.1").build() - ) - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") - - assertThat( - questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount - ).isEqualTo(0) - } + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemEditTextQuantityViewHolderFactory.create(parent) + } + + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } + + @Test + @UiThreadTest + fun shouldSetInputText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setQuantity( + Quantity.newBuilder().setValue(Decimal.newBuilder().setValue("5").build()) + ) + .build() + } + ) + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("5") + } + + @Test + @UiThreadTest + fun shouldSetInputTextToEmpty() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setQuantity( + Quantity.newBuilder().setValue(Decimal.newBuilder().setValue("5").build()) + ) + .build() + } + ) + ) {} + ) + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10") + + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value!!.quantity!!.value!!.value).isEqualTo("10.0") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerOneDecimalPlace() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("10.1") + + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value!!.quantity!!.value) + .isEqualTo(Decimal.newBuilder().setValue("10.1").build()) + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder().build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") + + assertThat(questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount).isEqualTo(0) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt index b23fa48844..7053902d90 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest.kt @@ -33,121 +33,125 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemEditTextSingleLineViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder - - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) - } - - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat(viewHolder.itemView.findViewById(R.id.question).text) - .isEqualTo("Question?") - } - - @Test - @UiThreadTest - fun shouldSetInputText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setStringValue( - com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) - .build() - } - ) - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("Answer") - } - - @Test - @UiThreadTest - fun shouldSetInputTextToEmpty() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setStringValue( - com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) - .build() - } - ) - ) {} - ) - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - - assertThat( - viewHolder.itemView.findViewById( - R.id.textInputEditText - ).text.toString() - ).isEqualTo("") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText) - .setText("Answer") - - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.stringValue.value).isEqualTo("Answer") - } - - @Test - @UiThreadTest - fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") - - assertThat( - questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount - ).isEqualTo(0) - } + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder + + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemEditTextSingleLineViewHolderFactory.create(parent) + } + + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat(viewHolder.itemView.findViewById(R.id.question).text) + .isEqualTo("Question?") + } + + @Test + @UiThreadTest + fun shouldSetInputText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setStringValue(com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) + .build() + } + ) + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("Answer") + } + + @Test + @UiThreadTest + fun shouldSetInputTextToEmpty() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setStringValue(com.google.fhir.r4.core.String.newBuilder().setValue("Answer")) + .build() + } + ) + ) {} + ) + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + + assertThat( + viewHolder.itemView.findViewById(R.id.textInputEditText).text.toString() + ) + .isEqualTo("") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("Answer") + + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.stringValue.value).isEqualTo("Answer") + } + + @Test + @UiThreadTest + fun shouldSetQuestionnaireResponseItemAnswerToEmpty() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.textInputEditText).setText("") + + assertThat(questionnaireItemViewItem.questionnaireResponseItemBuilder.answerCount).isEqualTo(0) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt index 6e53938673..9df8812eb5 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactoryInstrumentedTest.kt @@ -32,67 +32,67 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemGroupViewHolderFactoryInstrumentedTest { - private lateinit var context: ContextThemeWrapper - private lateinit var parent: FrameLayout - private lateinit var viewHolder: QuestionnaireItemViewHolder + private lateinit var context: ContextThemeWrapper + private lateinit var parent: FrameLayout + private lateinit var viewHolder: QuestionnaireItemViewHolder - @Before - fun setUp() { - context = ContextThemeWrapper( - InstrumentationRegistry.getInstrumentation().targetContext, - R.style.Theme_MaterialComponents - ) - parent = FrameLayout(context) - viewHolder = QuestionnaireItemGroupViewHolderFactory.create(parent) - } + @Before + fun setUp() { + context = + ContextThemeWrapper( + InstrumentationRegistry.getInstrumentation().targetContext, + R.style.Theme_MaterialComponents + ) + parent = FrameLayout(context) + viewHolder = QuestionnaireItemGroupViewHolderFactory.create(parent) + } - @Test - fun shouldSetTextViewText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = - com.google.fhir.r4.core.String.newBuilder().setValue("Group header").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Group header").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.group_header).text).isEqualTo( - "Group header" - ) - } + assertThat(viewHolder.itemView.findViewById(R.id.group_header).text) + .isEqualTo("Group header") + } - @Test - fun shouldSetTextViewVisible() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = - com.google.fhir.r4.core.String.newBuilder().setValue("Group header").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewVisible() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Group header").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.group_header).visibility - ).isEqualTo(View.VISIBLE) - } + assertThat(viewHolder.itemView.findViewById(R.id.group_header).visibility) + .isEqualTo(View.VISIBLE) + } - @Test - fun shouldSetTextViewGone() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun shouldSetTextViewGone() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { text = com.google.fhir.r4.core.String.newBuilder().setValue("").build() } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat( - viewHolder.itemView.findViewById(R.id.group_header).visibility - ).isEqualTo(View.GONE) - } + assertThat(viewHolder.itemView.findViewById(R.id.group_header).visibility) + .isEqualTo(View.GONE) + } } diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactoryInstrumentedTest.kt index 53277e056f..96f62de3de 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactoryInstrumentedTest.kt @@ -33,208 +33,315 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuestionnaireItemRadioGroupViewHolderFactoryInstrumentedTest { - private val parent = FrameLayout(InstrumentationRegistry.getInstrumentation().context) - private val viewHolder = QuestionnaireItemRadioGroupViewHolderFactory.create(parent) + private val parent = FrameLayout(InstrumentationRegistry.getInstrumentation().context) + private val viewHolder = QuestionnaireItemRadioGroupViewHolderFactory.create(parent) - @Test - fun bind_shouldSetHeaderText() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun bind_shouldSetHeaderText() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + text = com.google.fhir.r4.core.String.newBuilder().setValue("Question?").build() + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - assertThat(viewHolder.itemView.findViewById(R.id.radio_header).text).isEqualTo( - "Question?" - ) - } + assertThat(viewHolder.itemView.findViewById(R.id.radio_header).text) + .isEqualTo("Question?") + } - @Test - fun bind_shouldCreateRadioButtons() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAllAnswerOption(mutableListOf( - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() - .setValue("Coding 1") - .build() - }.build() - }.build() - }.build(), - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() - .setValue("Coding 2") - .build() - }.build() - }.build() - }.build() - )) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun bind_shouldCreateRadioButtons() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + addAllAnswerOption( + mutableListOf( + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() + .setValue("Coding 1") + .build() + } + .build() + } + .build() + } + .build(), + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() + .setValue("Coding 2") + .build() + } + .build() + } + .build() + } + .build() + ) + ) + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - val radioGroup = viewHolder.itemView.findViewById(R.id.radio_group) - assertThat(radioGroup.childCount).isEqualTo(2) - val radioButton1 = radioGroup.getChildAt(0) as RadioButton - assertThat(radioButton1.text).isEqualTo("Coding 1") - val radioButton2 = radioGroup.getChildAt(1) as RadioButton - assertThat(radioButton2.text).isEqualTo("Coding 2") - } + val radioGroup = viewHolder.itemView.findViewById(R.id.radio_group) + assertThat(radioGroup.childCount).isEqualTo(2) + val radioButton1 = radioGroup.getChildAt(0) as RadioButton + assertThat(radioButton1.text).isEqualTo("Coding 1") + val radioButton2 = radioGroup.getChildAt(1) as RadioButton + assertThat(radioButton2.text).isEqualTo("Coding 2") + } - @Test - fun bind_noAnswer_shouldLeaveRadioButtonsUnchecked() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAllAnswerOption(mutableListOf( - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() - .setValue("Coding 1") - .build() - }.build() - }.build() - }.build() - )) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) + @Test + fun bind_noAnswer_shouldLeaveRadioButtonsUnchecked() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + addAllAnswerOption( + mutableListOf( + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() + .setValue("Coding 1") + .build() + } + .build() + } + .build() + } + .build() + ) + ) + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) - val radioButton = - viewHolder.itemView.findViewById(R.id.radio_group) - .getChildAt(0) as RadioButton - assertThat(radioButton.isChecked).isFalse() - } + val radioButton = + viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0) as RadioButton + assertThat(radioButton.isChecked).isFalse() + } - @Test - @UiThreadTest - fun bind_answer_shouldCheckRadioButton() { - viewHolder.bind( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAllAnswerOption(mutableListOf( - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() - .setValue("Coding 1") - .build() - }.build() - }.build() - }.build(), - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() - .setValue("Coding 2") - .build() - }.build() - }.build() - }.build() - )) - }.build(), - QuestionnaireResponse.Item.newBuilder().apply { - addAnswer(QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() + @Test + @UiThreadTest + fun bind_answer_shouldCheckRadioButton() { + viewHolder.bind( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + addAllAnswerOption( + mutableListOf( + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() .setValue("Coding 1") .build() - }.build() - }.build() - }) - } - ) {} - ) + } + .build() + } + .build() + } + .build(), + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() + .setValue("Coding 2") + .build() + } + .build() + } + .build() + } + .build() + ) + ) + } + .build(), + QuestionnaireResponse.Item.newBuilder().apply { + addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder().setValue("Coding 1").build() + } + .build() + } + .build() + } + ) + } + ) {} + ) - assertThat( - (viewHolder.itemView.findViewById(R.id.radio_group) - .getChildAt(0) as RadioButton).isChecked - ).isTrue() - assertThat( - (viewHolder.itemView.findViewById(R.id.radio_group) - .getChildAt(1) as RadioButton).isChecked - ).isFalse() - } + assertThat( + (viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0) as + RadioButton) + .isChecked + ) + .isTrue() + assertThat( + (viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(1) as + RadioButton) + .isChecked + ) + .isFalse() + } - @Test - @UiThreadTest - fun click_shouldSetQuestionnaireResponseItemAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAllAnswerOption(mutableListOf( - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() + @Test + @UiThreadTest + fun click_shouldSetQuestionnaireResponseItemAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + addAllAnswerOption( + mutableListOf( + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() .setValue("Coding 1") .build() - }.build() - }.build() - }.build() - )) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0) - .performClick() + } + .build() + } + .build() + } + .build() + ) + ) + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0).performClick() - val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList - assertThat(answer.size).isEqualTo(1) - assertThat(answer[0].value.coding.display.value).isEqualTo("Coding 1") - } + val answer = questionnaireItemViewItem.questionnaireResponseItemBuilder.answerBuilderList + assertThat(answer.size).isEqualTo(1) + assertThat(answer[0].value.coding.display.value).isEqualTo("Coding 1") + } - @Test - @UiThreadTest - fun click_shouldCheckRadioButton() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder().apply { - addAllAnswerOption(mutableListOf( - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() + @Test + @UiThreadTest + fun click_shouldCheckRadioButton() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .apply { + addAllAnswerOption( + mutableListOf( + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() .setValue("Coding 1") .build() - }.build() - }.build() - }.build(), - Questionnaire.Item.AnswerOption.newBuilder().apply { - value = Questionnaire.Item.AnswerOption.ValueX.newBuilder().apply { - coding = Coding.newBuilder().apply { - display = com.google.fhir.r4.core.String.newBuilder() + } + .build() + } + .build() + } + .build(), + Questionnaire.Item.AnswerOption.newBuilder() + .apply { + value = + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .apply { + coding = + Coding.newBuilder() + .apply { + display = + com.google.fhir.r4.core.String.newBuilder() .setValue("Coding 2") .build() - }.build() - }.build() - }.build() - )) - }.build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - viewHolder.bind(questionnaireItemViewItem) - viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0) - .performClick() + } + .build() + } + .build() + } + .build() + ) + ) + } + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + viewHolder.bind(questionnaireItemViewItem) + viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0).performClick() - assertThat( - (viewHolder.itemView.findViewById(R.id.radio_group) - .getChildAt(0) as RadioButton).isChecked - ).isTrue() - assertThat( - (viewHolder.itemView.findViewById(R.id.radio_group) - .getChildAt(1) as RadioButton).isChecked - ).isFalse() - } + assertThat( + (viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(0) as + RadioButton) + .isChecked + ) + .isTrue() + assertThat( + (viewHolder.itemView.findViewById(R.id.radio_group).getChildAt(1) as + RadioButton) + .isChecked + ) + .isFalse() + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt index 89677ddccf..48e0c430f8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt @@ -20,26 +20,24 @@ import com.google.fhir.r4.core.Questionnaire.Item.AnswerOption import com.google.fhir.r4.core.QuestionnaireResponse.Item.Answer val AnswerOption.displayString: String - get() { - if (this.value.hasCoding()) { - val display = this.value.coding.display.value - return if (display.isEmpty()) { - this.value.coding.code.value - } else { - display - } - } else { - throw IllegalArgumentException("Answer option does not having coding.") - } + get() { + if (this.value.hasCoding()) { + val display = this.value.coding.display.value + return if (display.isEmpty()) { + this.value.coding.code.value + } else { + display + } + } else { + throw IllegalArgumentException("Answer option does not having coding.") } + } val AnswerOption.responseAnswerValueX: Answer.ValueX - get() { - if (this.value.hasCoding()) { - return Answer.ValueX.newBuilder() - .setCoding(this.value.coding) - .build() - } else { - throw IllegalArgumentException("Answer option does not having coding.") - } + get() { + if (this.value.hasCoding()) { + return Answer.ValueX.newBuilder().setCoding(this.value.coding).build() + } else { + throw IllegalArgumentException("Answer option does not having coding.") } + } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensions.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensions.kt index 2f4c07eec3..6f68ba1ec8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensions.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensions.kt @@ -22,22 +22,23 @@ internal const val ITEM_CONTROL_DROP_DOWN = "drop-down" internal const val ITEM_CONTROL_RADIO_BUTTON = "radio-button" internal const val EXTENSION_ITEM_CONTROL_URL = - "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl" -internal const val EXTENSION_ITEM_CONTROL_SYSTEM = - "http://hl7.org/fhir/questionnaire-item-control" + "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl" +internal const val EXTENSION_ITEM_CONTROL_SYSTEM = "http://hl7.org/fhir/questionnaire-item-control" // Item control code as string or null internal val Questionnaire.Item.itemControl: String? - get() { - return when ( - this.extensionList.firstOrNull { - it.url.value == EXTENSION_ITEM_CONTROL_URL - }?.value?.codeableConcept?.codingList?.firstOrNull { - it.system.value == EXTENSION_ITEM_CONTROL_SYSTEM - }?.code?.value - ) { - ITEM_CONTROL_DROP_DOWN -> ITEM_CONTROL_DROP_DOWN - ITEM_CONTROL_RADIO_BUTTON -> ITEM_CONTROL_RADIO_BUTTON - else -> null - } + get() { + return when (this.extensionList + .firstOrNull { it.url.value == EXTENSION_ITEM_CONTROL_URL } + ?.value + ?.codeableConcept + ?.codingList + ?.firstOrNull { it.system.value == EXTENSION_ITEM_CONTROL_SYSTEM } + ?.code + ?.value + ) { + ITEM_CONTROL_DROP_DOWN -> ITEM_CONTROL_DROP_DOWN + ITEM_CONTROL_RADIO_BUTTON -> ITEM_CONTROL_RADIO_BUTTON + else -> null } + } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemTypes.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemTypes.kt index 41aac6f994..63487edd8b 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemTypes.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemTypes.kt @@ -28,18 +28,18 @@ import com.google.fhir.shaded.protobuf.Message * [QuestionnaireItemTypeCode.Value.URL] do not have an explicit EnableWhen answer). */ fun Questionnaire.Item.EnableWhen.AnswerX.getValueForType( - type: Questionnaire.Item.TypeCode -): Message = when (val value = type.value) { + type: Questionnaire.Item.TypeCode +): Message = + when (val value = type.value) { QuestionnaireItemTypeCode.Value.DATE -> this.date QuestionnaireItemTypeCode.Value.BOOLEAN -> this.boolean QuestionnaireItemTypeCode.Value.DECIMAL -> this.decimal QuestionnaireItemTypeCode.Value.INTEGER -> this.integer QuestionnaireItemTypeCode.Value.DATE_TIME -> this.dateTime QuestionnaireItemTypeCode.Value.TIME -> this.time - QuestionnaireItemTypeCode.Value.STRING, QuestionnaireItemTypeCode.Value.TEXT -> - this.stringValue + QuestionnaireItemTypeCode.Value.STRING, QuestionnaireItemTypeCode.Value.TEXT -> this.stringValue else -> throw IllegalArgumentException("Unsupported value type $value") -} + } /** * Returns the value of the [QuestionnaireResponse.Item.Answer] for the [type]. @@ -47,9 +47,8 @@ fun Questionnaire.Item.EnableWhen.AnswerX.getValueForType( * @throws IllegalArgumentException if [type] is not supported (for example, questions of type * [QuestionnaireItemTypeCode.Value.GROUP] do not collect any answer). */ -fun QuestionnaireResponse.Item.Answer.getValueForType( - type: Questionnaire.Item.TypeCode -): Message = when (val value = type.value) { +fun QuestionnaireResponse.Item.Answer.getValueForType(type: Questionnaire.Item.TypeCode): Message = + when (val value = type.value) { QuestionnaireItemTypeCode.Value.DATE -> this.value.date QuestionnaireItemTypeCode.Value.BOOLEAN -> this.value.boolean QuestionnaireItemTypeCode.Value.DECIMAL -> this.value.decimal @@ -57,7 +56,7 @@ fun QuestionnaireResponse.Item.Answer.getValueForType( QuestionnaireItemTypeCode.Value.DATE_TIME -> this.value.dateTime QuestionnaireItemTypeCode.Value.TIME -> this.value.time QuestionnaireItemTypeCode.Value.STRING, QuestionnaireItemTypeCode.Value.TEXT -> - this.value.stringValue + this.value.stringValue QuestionnaireItemTypeCode.Value.URL -> this.value.uri else -> throw IllegalArgumentException("Unsupported value type $value") -} + } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt index 11f66ee8d1..238e572d55 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireFragment.kt @@ -30,42 +30,43 @@ import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.flow.collect class QuestionnaireFragment : Fragment() { - private val viewModel: QuestionnaireViewModel by viewModels() + private val viewModel: QuestionnaireViewModel by viewModels() - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - inflater.context.obtainStyledAttributes(R.styleable.QuestionnaireTheme).use { - val themeId = it.getResourceId( - // Use the custom questionnaire theme if it is specified - R.styleable.QuestionnaireTheme_questionnaire_theme, - // Otherwise, use the default questionnaire theme - R.style.Theme_Questionnaire - ) - return inflater - .cloneInContext(ContextThemeWrapper(inflater.context, themeId)) - .inflate(R.layout.questionnaire_fragment, container, false) - } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + inflater.context.obtainStyledAttributes(R.styleable.QuestionnaireTheme).use { + val themeId = + it.getResourceId( + // Use the custom questionnaire theme if it is specified + R.styleable.QuestionnaireTheme_questionnaire_theme, + // Otherwise, use the default questionnaire theme + R.style.Theme_Questionnaire + ) + return inflater + .cloneInContext(ContextThemeWrapper(inflater.context, themeId)) + .inflate(R.layout.questionnaire_fragment, container, false) } + } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val recyclerView = view.findViewById(R.id.recycler_view) - val adapter = QuestionnaireItemAdapter() - recyclerView.adapter = adapter - recyclerView.layoutManager = LinearLayoutManager(view.context) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val recyclerView = view.findViewById(R.id.recycler_view) + val adapter = QuestionnaireItemAdapter() + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(view.context) - // Listen to updates from the view model. - viewLifecycleOwner.lifecycleScope.launchWhenCreated { - viewModel.questionnaireItemViewItemListFlow.collect { adapter.submitList(it) } - } + // Listen to updates from the view model. + viewLifecycleOwner.lifecycleScope.launchWhenCreated { + viewModel.questionnaireItemViewItemListFlow.collect { adapter.submitList(it) } } + } - // Returns the current questionnaire response - fun getQuestionnaireResponse() = viewModel.getQuestionnaireResponse() + // Returns the current questionnaire response + fun getQuestionnaireResponse() = viewModel.getQuestionnaireResponse() - companion object { - const val BUNDLE_KEY_QUESTIONNAIRE = "questionnaire" - } + companion object { + const val BUNDLE_KEY_QUESTIONNAIRE = "questionnaire" + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt index 14af77fc4c..97fd9c9633 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapter.kt @@ -37,105 +37,95 @@ import com.google.fhir.r4.core.Questionnaire import com.google.fhir.r4.core.QuestionnaireItemTypeCode internal class QuestionnaireItemAdapter : - ListAdapter(DiffCallback) { - /** - * @param viewType the integer value of the [QuestionnaireItemViewHolderType] used to render the - * [QuestionnaireItemViewItem]. - */ - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionnaireItemViewHolder { - val viewHolderFactory = when (QuestionnaireItemViewHolderType.fromInt(viewType)) { - QuestionnaireItemViewHolderType.GROUP -> QuestionnaireItemGroupViewHolderFactory - QuestionnaireItemViewHolderType.CHECK_BOX -> QuestionnaireItemCheckBoxViewHolderFactory - QuestionnaireItemViewHolderType.DATE_PICKER -> - QuestionnaireItemDatePickerViewHolderFactory - QuestionnaireItemViewHolderType.DATE_TIME_PICKER -> - QuestionnaireItemDateTimePickerViewHolderFactory - QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE -> - QuestionnaireItemEditTextSingleLineViewHolderFactory - QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE -> - QuestionnaireItemEditTextMultiLineViewHolderFactory - QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER -> - QuestionnaireItemEditTextIntegerViewHolderFactory - QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL -> - QuestionnaireItemEditTextDecimalViewHolderFactory - QuestionnaireItemViewHolderType.RADIO_GROUP -> - QuestionnaireItemRadioGroupViewHolderFactory - QuestionnaireItemViewHolderType.DROP_DOWN -> - QuestionnaireItemDropDownViewHolderFactory - QuestionnaireItemViewHolderType.DISPLAY -> - QuestionnaireItemDisplayViewHolderFactory - QuestionnaireItemViewHolderType.QUANTITY -> - QuestionnaireItemEditTextQuantityViewHolderFactory - } - return viewHolderFactory.create(parent) - } + ListAdapter(DiffCallback) { + /** + * @param viewType the integer value of the [QuestionnaireItemViewHolderType] used to render the + * [QuestionnaireItemViewItem]. + */ + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuestionnaireItemViewHolder { + val viewHolderFactory = + when (QuestionnaireItemViewHolderType.fromInt(viewType)) { + QuestionnaireItemViewHolderType.GROUP -> QuestionnaireItemGroupViewHolderFactory + QuestionnaireItemViewHolderType.CHECK_BOX -> QuestionnaireItemCheckBoxViewHolderFactory + QuestionnaireItemViewHolderType.DATE_PICKER -> QuestionnaireItemDatePickerViewHolderFactory + QuestionnaireItemViewHolderType.DATE_TIME_PICKER -> + QuestionnaireItemDateTimePickerViewHolderFactory + QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE -> + QuestionnaireItemEditTextSingleLineViewHolderFactory + QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE -> + QuestionnaireItemEditTextMultiLineViewHolderFactory + QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER -> + QuestionnaireItemEditTextIntegerViewHolderFactory + QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL -> + QuestionnaireItemEditTextDecimalViewHolderFactory + QuestionnaireItemViewHolderType.RADIO_GROUP -> QuestionnaireItemRadioGroupViewHolderFactory + QuestionnaireItemViewHolderType.DROP_DOWN -> QuestionnaireItemDropDownViewHolderFactory + QuestionnaireItemViewHolderType.DISPLAY -> QuestionnaireItemDisplayViewHolderFactory + QuestionnaireItemViewHolderType.QUANTITY -> + QuestionnaireItemEditTextQuantityViewHolderFactory + } + return viewHolderFactory.create(parent) + } - override fun onBindViewHolder(holder: QuestionnaireItemViewHolder, position: Int) { - holder.bind(getItem(position)) - } + override fun onBindViewHolder(holder: QuestionnaireItemViewHolder, position: Int) { + holder.bind(getItem(position)) + } - /** - * Returns the integer value of the [QuestionnaireItemViewHolderType] that will be used to - * render the [QuestionnaireItemViewItem]. This is determined by a combination of the data type - * of the question and any additional Questionnaire Item UI Control Codes - * (http://hl7.org/fhir/R4/valueset-questionnaire-item-control.html) used in the - * itemControl extension (http://hl7.org/fhir/R4/extension-questionnaire-itemcontrol.html). - */ - override fun getItemViewType(position: Int): Int { - val questionnaireItem = getItem(position).questionnaireItem - return when (val type = questionnaireItem.type.value) { - QuestionnaireItemTypeCode.Value.GROUP -> QuestionnaireItemViewHolderType.GROUP - QuestionnaireItemTypeCode.Value.BOOLEAN -> QuestionnaireItemViewHolderType.CHECK_BOX - QuestionnaireItemTypeCode.Value.DATE -> QuestionnaireItemViewHolderType.DATE_PICKER - QuestionnaireItemTypeCode.Value.DATE_TIME -> - QuestionnaireItemViewHolderType.DATE_TIME_PICKER - QuestionnaireItemTypeCode.Value.STRING -> - QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE - QuestionnaireItemTypeCode.Value.TEXT -> - QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE - QuestionnaireItemTypeCode.Value.INTEGER -> - QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER - QuestionnaireItemTypeCode.Value.DECIMAL -> - QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL - QuestionnaireItemTypeCode.Value.CHOICE -> - getChoiceViewHolderType(questionnaireItem) - QuestionnaireItemTypeCode.Value.DISPLAY -> - QuestionnaireItemViewHolderType.DISPLAY - QuestionnaireItemTypeCode.Value.QUANTITY -> - QuestionnaireItemViewHolderType.QUANTITY - else -> throw NotImplementedError("Question type $type not supported.") - }.value - } + /** + * Returns the integer value of the [QuestionnaireItemViewHolderType] that will be used to render + * the [QuestionnaireItemViewItem]. This is determined by a combination of the data type of the + * question and any additional Questionnaire Item UI Control Codes + * (http://hl7.org/fhir/R4/valueset-questionnaire-item-control.html) used in the itemControl + * extension (http://hl7.org/fhir/R4/extension-questionnaire-itemcontrol.html). + */ + override fun getItemViewType(position: Int): Int { + val questionnaireItem = getItem(position).questionnaireItem + return when (val type = questionnaireItem.type.value) { + QuestionnaireItemTypeCode.Value.GROUP -> QuestionnaireItemViewHolderType.GROUP + QuestionnaireItemTypeCode.Value.BOOLEAN -> QuestionnaireItemViewHolderType.CHECK_BOX + QuestionnaireItemTypeCode.Value.DATE -> QuestionnaireItemViewHolderType.DATE_PICKER + QuestionnaireItemTypeCode.Value.DATE_TIME -> QuestionnaireItemViewHolderType.DATE_TIME_PICKER + QuestionnaireItemTypeCode.Value.STRING -> + QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE + QuestionnaireItemTypeCode.Value.TEXT -> QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE + QuestionnaireItemTypeCode.Value.INTEGER -> QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER + QuestionnaireItemTypeCode.Value.DECIMAL -> QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL + QuestionnaireItemTypeCode.Value.CHOICE -> getChoiceViewHolderType(questionnaireItem) + QuestionnaireItemTypeCode.Value.DISPLAY -> QuestionnaireItemViewHolderType.DISPLAY + QuestionnaireItemTypeCode.Value.QUANTITY -> QuestionnaireItemViewHolderType.QUANTITY + else -> throw NotImplementedError("Question type $type not supported.") + }.value + } - private fun getChoiceViewHolderType(questionnaireItem: Questionnaire.Item): - QuestionnaireItemViewHolderType { - if (questionnaireItem.itemControl == ITEM_CONTROL_DROP_DOWN) { - return QuestionnaireItemViewHolderType.DROP_DOWN - } else if (questionnaireItem.itemControl == ITEM_CONTROL_RADIO_BUTTON) { - return QuestionnaireItemViewHolderType.RADIO_GROUP - } else if ( - questionnaireItem.answerOptionCount >= - MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN) { - return QuestionnaireItemViewHolderType.DROP_DOWN - } else { - return QuestionnaireItemViewHolderType.RADIO_GROUP - } + private fun getChoiceViewHolderType( + questionnaireItem: Questionnaire.Item + ): QuestionnaireItemViewHolderType { + if (questionnaireItem.itemControl == ITEM_CONTROL_DROP_DOWN) { + return QuestionnaireItemViewHolderType.DROP_DOWN + } else if (questionnaireItem.itemControl == ITEM_CONTROL_RADIO_BUTTON) { + return QuestionnaireItemViewHolderType.RADIO_GROUP + } else if (questionnaireItem.answerOptionCount >= MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN + ) { + return QuestionnaireItemViewHolderType.DROP_DOWN + } else { + return QuestionnaireItemViewHolderType.RADIO_GROUP } + } - internal companion object { - // Choice questions are rendered as radio group if number of options less than this constant - const val MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN = 4 - } + internal companion object { + // Choice questions are rendered as radio group if number of options less than this constant + const val MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN = 4 + } } internal object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: QuestionnaireItemViewItem, - newItem: QuestionnaireItemViewItem - ) = oldItem.questionnaireItem.linkId == newItem.questionnaireItem.linkId + override fun areItemsTheSame( + oldItem: QuestionnaireItemViewItem, + newItem: QuestionnaireItemViewItem + ) = oldItem.questionnaireItem.linkId == newItem.questionnaireItem.linkId - override fun areContentsTheSame( - oldItem: QuestionnaireItemViewItem, - newItem: QuestionnaireItemViewItem - ) = oldItem.questionnaireItem == newItem.questionnaireItem + override fun areContentsTheSame( + oldItem: QuestionnaireItemViewItem, + newItem: QuestionnaireItemViewItem + ) = oldItem.questionnaireItem == newItem.questionnaireItem } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderType.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderType.kt index 39e778c739..df26b2f757 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderType.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderType.kt @@ -27,21 +27,21 @@ package com.google.android.fhir.datacapture * http://hl7.org/fhir/R4/valueset-questionnaire-item-control.html. */ internal enum class QuestionnaireItemViewHolderType(val value: Int) { - GROUP(0), - CHECK_BOX(1), - DATE_PICKER(2), - DATE_TIME_PICKER(3), - EDIT_TEXT_SINGLE_LINE(4), - EDIT_TEXT_MULTI_LINE(5), - EDIT_TEXT_INTEGER(6), - EDIT_TEXT_DECIMAL(7), - RADIO_GROUP(8), - DROP_DOWN(9), - DISPLAY(10), - QUANTITY(11); + GROUP(0), + CHECK_BOX(1), + DATE_PICKER(2), + DATE_TIME_PICKER(3), + EDIT_TEXT_SINGLE_LINE(4), + EDIT_TEXT_MULTI_LINE(5), + EDIT_TEXT_INTEGER(6), + EDIT_TEXT_DECIMAL(7), + RADIO_GROUP(8), + DROP_DOWN(9), + DISPLAY(10), + QUANTITY(11); - companion object { - private val VALUES = values() - fun fromInt(value: Int) = VALUES[value] - } + companion object { + private val VALUES = values() + fun fromInt(value: Int) = VALUES[value] + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt index c8de0afa14..055243ea4d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/QuestionnaireViewModel.kt @@ -29,110 +29,107 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map internal class QuestionnaireViewModel(state: SavedStateHandle) : ViewModel() { - /** The current questionnaire as questions are being answered. */ - private val questionnaire: Questionnaire - - init { - val questionnaireJson: String = state[QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE]!! - val builder = Questionnaire.newBuilder() - questionnaire = JsonFormat.getParser().merge(questionnaireJson, builder).build() + /** The current questionnaire as questions are being answered. */ + private val questionnaire: Questionnaire + + init { + val questionnaireJson: String = state[QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE]!! + val builder = Questionnaire.newBuilder() + questionnaire = JsonFormat.getParser().merge(questionnaireJson, builder).build() + } + + /** The current questionnaire response as questions are being answered. */ + private val questionnaireResponseBuilder = QuestionnaireResponse.newBuilder() + + init { + questionnaireResponseBuilder.questionnaire = + Canonical.newBuilder().setValue(questionnaire.id.value).build() + // Retain the hierarchy and order of items within the questionnaire as specified in the + // standard. See https://www.hl7.org/fhir/questionnaireresponse.html#notes. + questionnaire.itemList.forEach { + questionnaireResponseBuilder.addItem(it.createQuestionnaireResponseItem()) } - - /** The current questionnaire response as questions are being answered. */ - private val questionnaireResponseBuilder = QuestionnaireResponse.newBuilder() - - init { - questionnaireResponseBuilder.questionnaire = - Canonical.newBuilder().setValue(questionnaire.id.value).build() - // Retain the hierarchy and order of items within the questionnaire as specified in the - // standard. See https://www.hl7.org/fhir/questionnaireresponse.html#notes. - questionnaire.itemList.forEach { - questionnaireResponseBuilder.addItem(it.createQuestionnaireResponseItem()) - } + } + + /** Map from link IDs to questionnaire response items. */ + private val linkIdToQuestionnaireResponseItemMap = + createLinkIdToQuestionnaireResponseItemMap(questionnaireResponseBuilder.itemBuilderList) + + /** Tracks modifications in order to update the UI. */ + private val modificationCount = MutableStateFlow(0) + + /** Callback function to update the UI. */ + private val questionnaireResponseItemChangedCallback = { modificationCount.value += 1 } + + internal val questionnaireItemViewItemList + get() = + getQuestionnaireItemViewItemList( + questionnaire.itemList, + questionnaireResponseBuilder.itemBuilderList + ) + + /** [QuestionnaireItemViewItem] s to be displayed in the UI. */ + internal val questionnaireItemViewItemListFlow: Flow> = + modificationCount.map { questionnaireItemViewItemList } + + /** The current [QuestionnaireResponse] captured by the UI. */ + fun getQuestionnaireResponse(): QuestionnaireResponse = questionnaireResponseBuilder.build() + + private fun createLinkIdToQuestionnaireResponseItemMap( + questionnaireResponseItemList: List + ): Map { + val linkIdToQuestionnaireResponseItemMap = + questionnaireResponseItemList.map { it.linkId.value to it }.toMap().toMutableMap() + for (item in questionnaireResponseItemList) { + linkIdToQuestionnaireResponseItemMap.putAll( + createLinkIdToQuestionnaireResponseItemMap(item.itemBuilderList) + ) } - - /** Map from link IDs to questionnaire response items. */ - private val linkIdToQuestionnaireResponseItemMap = - createLinkIdToQuestionnaireResponseItemMap( - questionnaireResponseBuilder.itemBuilderList + return linkIdToQuestionnaireResponseItemMap + } + + /** + * Traverse (DFS) through the list of questionnaire items and the list of questionnaire response + * items and populate [questionnaireItemViewItemList] with matching pairs of questionnaire item + * and questionnaire response item. + * + * The traverse is carried out in the two lists in tandem. The two lists should be structurally + * identical. + */ + private fun getQuestionnaireItemViewItemList( + questionnaireItemList: List, + questionnaireResponseItemList: List + ): List { + val questionnaireItemViewItemList = mutableListOf() + val questionnaireItemListIterator = questionnaireItemList.iterator() + val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator() + while (questionnaireItemListIterator.hasNext() && + questionnaireResponseItemListIterator.hasNext()) { + val questionnaireItem = questionnaireItemListIterator.next() + val questionnaireResponseItem = questionnaireResponseItemListIterator.next() + + val enabled = + EnablementEvaluator.evaluate(questionnaireItem) { + (linkIdToQuestionnaireResponseItemMap[it] ?: return@evaluate null).build() + } + if (enabled) { + questionnaireItemViewItemList.add( + QuestionnaireItemViewItem( + questionnaireItem, + questionnaireResponseItem, + questionnaireResponseItemChangedCallback + ) ) - - /** Tracks modifications in order to update the UI. */ - private val modificationCount = MutableStateFlow(0) - - /** Callback function to update the UI. */ - private val questionnaireResponseItemChangedCallback = { modificationCount.value += 1 } - - internal val questionnaireItemViewItemList - get() = getQuestionnaireItemViewItemList( - questionnaire.itemList, - questionnaireResponseBuilder.itemBuilderList + questionnaireItemViewItemList.addAll( + getQuestionnaireItemViewItemList( + questionnaireItem.itemList, + questionnaireResponseItem.itemBuilderList + ) ) - - /** [QuestionnaireItemViewItem]s to be displayed in the UI. */ - internal val questionnaireItemViewItemListFlow: Flow> = - modificationCount.map { questionnaireItemViewItemList } - - /** The current [QuestionnaireResponse] captured by the UI. */ - fun getQuestionnaireResponse(): QuestionnaireResponse = questionnaireResponseBuilder.build() - - private fun createLinkIdToQuestionnaireResponseItemMap( - questionnaireResponseItemList: List - ): Map { - val linkIdToQuestionnaireResponseItemMap = questionnaireResponseItemList.map { - it.linkId.value to it - }.toMap().toMutableMap() - for (item in questionnaireResponseItemList) { - linkIdToQuestionnaireResponseItemMap.putAll( - createLinkIdToQuestionnaireResponseItemMap(item.itemBuilderList) - ) - } - return linkIdToQuestionnaireResponseItemMap - } - - /** - * Traverse (DFS) through the list of questionnaire items and the list of questionnaire response - * items and populate [questionnaireItemViewItemList] with matching pairs of questionnaire item - * and questionnaire response item. - * - * The traverse is carried out in the two lists in tandem. The two lists should be structurally - * identical. - */ - private fun getQuestionnaireItemViewItemList( - questionnaireItemList: List, - questionnaireResponseItemList: List - ): List { - val questionnaireItemViewItemList = mutableListOf() - val questionnaireItemListIterator = questionnaireItemList.iterator() - val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator() - while ( - questionnaireItemListIterator.hasNext() && - questionnaireResponseItemListIterator.hasNext() - ) { - val questionnaireItem = questionnaireItemListIterator.next() - val questionnaireResponseItem = questionnaireResponseItemListIterator.next() - - val enabled = EnablementEvaluator.evaluate(questionnaireItem) { - (linkIdToQuestionnaireResponseItemMap[it] ?: return@evaluate null).build() - } - if (enabled) { - questionnaireItemViewItemList.add( - QuestionnaireItemViewItem( - questionnaireItem, - questionnaireResponseItem, - questionnaireResponseItemChangedCallback - ) - ) - questionnaireItemViewItemList.addAll( - getQuestionnaireItemViewItemList( - questionnaireItem.itemList, - questionnaireResponseItem.itemBuilderList - ) - ) - } - } - return questionnaireItemViewItemList + } } + return questionnaireItemViewItemList + } } /** @@ -143,12 +140,14 @@ internal class QuestionnaireViewModel(state: SavedStateHandle) : ViewModel() { * https://www.hl7.org/fhir/questionnaireresponse.html#notes for more details. */ private fun Questionnaire.Item.createQuestionnaireResponseItem(): - QuestionnaireResponse.Item.Builder { - return QuestionnaireResponse.Item.newBuilder().apply { - linkId = com.google.fhir.r4.core.String.newBuilder() - .setValue(this@createQuestionnaireResponseItem.linkId.value).build() - this@createQuestionnaireResponseItem.itemList.forEach { - this.addItem(it.createQuestionnaireResponseItem()) - } + QuestionnaireResponse.Item.Builder { + return QuestionnaireResponse.Item.newBuilder().apply { + linkId = + com.google.fhir.r4.core.String.newBuilder() + .setValue(this@createQuestionnaireResponseItem.linkId.value) + .build() + this@createQuestionnaireResponseItem.itemList.forEach { + this.addItem(it.createQuestionnaireResponseItem()) } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluator.kt index e75f3b56ef..58e66a4be2 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluator.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluator.kt @@ -26,9 +26,10 @@ import java.lang.IllegalStateException /** * Evaluator for the enablement status of a [Questionnaire.Item]. Uses the `enableWhen` constraints * and the `enableBehavior` value defined in the [Questionnaire.Item]. Also depends on the answers - * (or lack thereof) captured in the specified [QuestionnaireResponse.Item]s. + * (or lack thereof) captured in the specified [QuestionnaireResponse.Item] s. * * For example, the following `enableWhen` constraint in a [Questionnaire.Item] + * ``` * "enableWhen": [ * { * "question": "vitaminKgiven", @@ -36,6 +37,7 @@ import java.lang.IllegalStateException * "answerBoolean": true * } * ], + * ``` * specifies that the [Questionnaire.Item] should be enabled only if the question with ID * `vitaminKgiven` has been answered. * @@ -43,76 +45,75 @@ import java.lang.IllegalStateException * However, it is also possible that only user interaction is enabled or disabled (e.g. grayed out) * with the [Questionnaire.Item] always shown. * - * For more information see [Questionnaire.item.enableWhen](https://www.hl7.org/fhir/questionnaire-definitions.html#Questionnaire.item.enableWhen) - * and [Questionnaire.item.enableBehavior](https://www.hl7.org/fhir/questionnaire-definitions.html#Questionnaire.item.enableBehavior). + * For more information see + * [Questionnaire.item.enableWhen](https://www.hl7.org/fhir/questionnaire-definitions.html#Questionnaire.item.enableWhen) + * and + * [Questionnaire.item.enableBehavior](https://www.hl7.org/fhir/questionnaire-definitions.html#Questionnaire.item.enableBehavior) + * . */ internal object EnablementEvaluator { - /** - * Returns whether [questionnaireItem] should be enabled. - * - * @param questionnaireResponseItemRetriever function that returns the - * [QuestionnaireResponse.Item] with the `linkId`, or null if there isn't one. - * - * For example, the questionnaireItem might be - */ - fun evaluate( - questionnaireItem: Questionnaire.Item, - questionnaireResponseItemRetriever: (linkId: String) -> QuestionnaireResponse.Item? - ): Boolean { - val enableWhenList = questionnaireItem.enableWhenList + /** + * Returns whether [questionnaireItem] should be enabled. + * + * @param questionnaireResponseItemRetriever function that returns the + * [QuestionnaireResponse.Item] with the `linkId`, or null if there isn't one. + * + * For example, the questionnaireItem might be + */ + fun evaluate( + questionnaireItem: Questionnaire.Item, + questionnaireResponseItemRetriever: (linkId: String) -> QuestionnaireResponse.Item? + ): Boolean { + val enableWhenList = questionnaireItem.enableWhenList - // The questionnaire item is enabled by default if there is no `enableWhen` constraint. - if (enableWhenList.isEmpty()) return true + // The questionnaire item is enabled by default if there is no `enableWhen` constraint. + if (enableWhenList.isEmpty()) return true - // Evaluate single `enableWhen` constraint. - if (enableWhenList.size == 1) { - return evaluateEnableWhen( - questionnaireItem.type, - enableWhenList.single(), - questionnaireResponseItemRetriever - ) - } + // Evaluate single `enableWhen` constraint. + if (enableWhenList.size == 1) { + return evaluateEnableWhen( + questionnaireItem.type, + enableWhenList.single(), + questionnaireResponseItemRetriever + ) + } - // Evaluate multiple `enableWhen` constraints and aggregate the results according to - // `enableBehavior` which specifies one of the two behaviors: 1) the questionnaire item is - // enabled if ALL `enableWhen` constraints are satisfied, or 2) the questionnaire item is - // enabled if ANY `enableWhen` constraint is satisfied. - return when (val value = questionnaireItem.enableBehavior.value) { - EnableWhenBehaviorCode.Value.ALL -> - enableWhenList.all { - evaluateEnableWhen( - questionnaireItem.type, it, questionnaireResponseItemRetriever) - } - EnableWhenBehaviorCode.Value.ANY -> - enableWhenList.any { - evaluateEnableWhen( - questionnaireItem.type, it, questionnaireResponseItemRetriever) - } - else -> - throw IllegalStateException("Unrecognized enable when behavior $value") + // Evaluate multiple `enableWhen` constraints and aggregate the results according to + // `enableBehavior` which specifies one of the two behaviors: 1) the questionnaire item is + // enabled if ALL `enableWhen` constraints are satisfied, or 2) the questionnaire item is + // enabled if ANY `enableWhen` constraint is satisfied. + return when (val value = questionnaireItem.enableBehavior.value) { + EnableWhenBehaviorCode.Value.ALL -> + enableWhenList.all { + evaluateEnableWhen(questionnaireItem.type, it, questionnaireResponseItemRetriever) } + EnableWhenBehaviorCode.Value.ANY -> + enableWhenList.any { + evaluateEnableWhen(questionnaireItem.type, it, questionnaireResponseItemRetriever) + } + else -> throw IllegalStateException("Unrecognized enable when behavior $value") } + } - /** - * Returns whether the `enableWhen` constraint is satisfied. - * - * @param questionnaireResponseItemRetriever function that returns the - * [QuestionnaireResponse.Item] with the `linkId`, or null if there isn't one. - */ - private fun evaluateEnableWhen( - type: Questionnaire.Item.TypeCode, - enableWhen: Questionnaire.Item.EnableWhen, - questionnaireResponseItemRetriever: (linkId: String) -> QuestionnaireResponse.Item? - ): Boolean { - val responseItem = - questionnaireResponseItemRetriever(enableWhen.question.value) ?: return true - return if (QuestionnaireItemOperatorCode.Value.EXISTS == enableWhen.operator.value) { - (responseItem.answerCount > 0) == enableWhen.answer.boolean.value - } else { - responseItem.contains(enableWhenTypeToPredicate(enableWhen, type)) - } + /** + * Returns whether the `enableWhen` constraint is satisfied. + * + * @param questionnaireResponseItemRetriever function that returns the + * [QuestionnaireResponse.Item] with the `linkId`, or null if there isn't one. + */ + private fun evaluateEnableWhen( + type: Questionnaire.Item.TypeCode, + enableWhen: Questionnaire.Item.EnableWhen, + questionnaireResponseItemRetriever: (linkId: String) -> QuestionnaireResponse.Item? + ): Boolean { + val responseItem = questionnaireResponseItemRetriever(enableWhen.question.value) ?: return true + return if (QuestionnaireItemOperatorCode.Value.EXISTS == enableWhen.operator.value) { + (responseItem.answerCount > 0) == enableWhen.answer.boolean.value + } else { + responseItem.contains(enableWhenTypeToPredicate(enableWhen, type)) } + } } /** @@ -121,11 +122,9 @@ internal object EnablementEvaluator { * @param predicate boolean predicate function that takes a [QuestionnaireResponse.Item.Answer]. */ private fun QuestionnaireResponse.Item.contains( - predicate: (QuestionnaireResponse.Item.Answer) -> Boolean + predicate: (QuestionnaireResponse.Item.Answer) -> Boolean ): Boolean { - return this.answerList.any { - predicate(it) - } + return this.answerList.any { predicate(it) } } /** @@ -134,15 +133,17 @@ private fun QuestionnaireResponse.Item.contains( * @param type used to get value based on [Questionnaire.Item.TypeCode]. */ private fun enableWhenTypeToPredicate( - enableWhen: Questionnaire.Item.EnableWhen, - type: Questionnaire.Item.TypeCode + enableWhen: Questionnaire.Item.EnableWhen, + type: Questionnaire.Item.TypeCode ): (QuestionnaireResponse.Item.Answer) -> Boolean { - val enableWhenAnswerValue = enableWhen.answer.getValueForType(type) - when (val operator = enableWhen.operator.value) { - QuestionnaireItemOperatorCode.Value.EQUALS -> - return { it.getValueForType(type) == enableWhenAnswerValue } - QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO -> - return { it.getValueForType(type) != enableWhenAnswerValue } - else -> throw NotImplementedError("Enable when operator $operator is not implemented.") - } + val enableWhenAnswerValue = enableWhen.answer.getValueForType(type) + when (val operator = enableWhen.operator.value) { + QuestionnaireItemOperatorCode.Value.EQUALS -> return { + it.getValueForType(type) == enableWhenAnswerValue + } + QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO -> return { + it.getValueForType(type) != enableWhenAnswerValue + } + else -> throw NotImplementedError("Enable when operator $operator is not implemented.") + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt index de55f3ffe5..9a467351a6 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/mapping/ResourceMapper.kt @@ -30,15 +30,16 @@ import com.google.fhir.r4.core.Url import com.google.fhir.shaded.protobuf.Message /** - * Maps [QuestionnaireResponse]s to FHIR resources and vice versa. + * Maps [QuestionnaireResponse] s to FHIR resources and vice versa. * - * The process of converting [QuestionnaireResponse]s to other FHIR resources is called + * The process of converting [QuestionnaireResponse] s to other FHIR resources is called * [extraction](http://build.fhir.org/ig/HL7/sdc/extraction.html). The reverse process of converting - * existing FHIR resources to [QuestionnaireResponse]s to be used to pre-fill the UI is called + * existing FHIR resources to [QuestionnaireResponse] s to be used to pre-fill the UI is called * [population](http://build.fhir.org/ig/HL7/sdc/populate.html). * * [Definition-based extraction](http://build.fhir.org/ig/HL7/sdc/extraction.html#definition-based-extraction) - * and [expression-based population](http://build.fhir.org/ig/HL7/sdc/populate.html#expression-based-population) + * and + * [expression-based population](http://build.fhir.org/ig/HL7/sdc/populate.html#expression-based-population) * are used because these approaches are generic enough to work with any FHIR resource types, and at * the same time relatively easy to implement. * @@ -46,25 +47,20 @@ import com.google.fhir.shaded.protobuf.Message */ internal object ResourceMapper { - /** - * Extract a FHIR resource from the `questionnaire` and `questionnaireResponse`. - * - * This method assumes there is only one FHIR resource to be extracted from the given - * `questionnaire` and `questionnaireResponse`. - */ - fun extract( - questionnaire: Questionnaire, - questionnaireResponse: QuestionnaireResponse - ): Message { - val builder = questionnaire.itemContextNameToExpressionMap.values.first().let { - Class.forName("com.google.fhir.r4.core.$it") - .getMethod("newBuilder") - .invoke(null) as Message.Builder - } - return builder - .extractFields(questionnaire.itemList, questionnaireResponse.itemList) - .build() - } + /** + * Extract a FHIR resource from the `questionnaire` and `questionnaireResponse`. + * + * This method assumes there is only one FHIR resource to be extracted from the given + * `questionnaire` and `questionnaireResponse`. + */ + fun extract(questionnaire: Questionnaire, questionnaireResponse: QuestionnaireResponse): Message { + val builder = + questionnaire.itemContextNameToExpressionMap.values.first().let { + Class.forName("com.google.fhir.r4.core.$it").getMethod("newBuilder").invoke(null) as + Message.Builder + } + return builder.extractFields(questionnaire.itemList, questionnaireResponse.itemList).build() + } } /** @@ -72,21 +68,19 @@ internal object ResourceMapper { * [questionnaireItemList] and [questionnaireResponseItemList]. */ private fun Message.Builder.extractFields( - questionnaireItemList: List, - questionnaireResponseItemList: List + questionnaireItemList: List, + questionnaireResponseItemList: List ): Message.Builder { - val questionnaireItemListIterator = questionnaireItemList.iterator() - val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator() - while ( - questionnaireItemListIterator.hasNext() && - questionnaireResponseItemListIterator.hasNext() - ) { - val questionnaireItem = questionnaireItemListIterator.next() - val questionnaireResponseItem = questionnaireResponseItemListIterator.next() - this.extractField(questionnaireItem, questionnaireResponseItem) - extractFields(questionnaireItem.itemList, questionnaireResponseItem.itemList) - } - return this + val questionnaireItemListIterator = questionnaireItemList.iterator() + val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator() + while (questionnaireItemListIterator.hasNext() && + questionnaireResponseItemListIterator.hasNext()) { + val questionnaireItem = questionnaireItemListIterator.next() + val questionnaireResponseItem = questionnaireResponseItemListIterator.next() + this.extractField(questionnaireItem, questionnaireResponseItem) + extractFields(questionnaireItem.itemList, questionnaireResponseItem.itemList) + } + return this } /** @@ -96,20 +90,22 @@ private fun Message.Builder.extractFields( * NOTE: Nested fields are not handled. See https://github.com/google/android-fhir/issues/240. */ private fun Message.Builder.extractField( - questionnaireItem: Questionnaire.Item, - questionnaireResponseItem: QuestionnaireResponse.Item + questionnaireItem: Questionnaire.Item, + questionnaireResponseItem: QuestionnaireResponse.Item ) { - val targetFieldName = questionnaireItem.definitionFieldName - if (targetFieldName.isEmpty()) { - return - } + val targetFieldName = questionnaireItem.definitionFieldName + if (targetFieldName.isEmpty()) { + return + } - questionnaireItem.type.getClass()?.let { - this.javaClass.getMethod("set${targetFieldName.capitalize()}", it).invoke( - this, - questionnaireResponseItem.answerList.single().getValueForType(questionnaireItem.type) - ) - } + questionnaireItem.type.getClass()?.let { + this.javaClass + .getMethod("set${targetFieldName.capitalize()}", it) + .invoke( + this, + questionnaireResponseItem.answerList.single().getValueForType(questionnaireItem.type) + ) + } } /** @@ -120,14 +116,15 @@ private fun Message.Builder.extractField( * `"birthDate"`. */ private val Questionnaire.Item.definitionFieldName - get() = this.definition.value.substringAfterLast(".") + get() = this.definition.value.substringAfterLast(".") /** * Returns the [Class] for the answer to the [Questionnaire.Item]. * * Used to retrieve the method to invoke to set the field in the extracted FHIR resource. */ -private fun Questionnaire.Item.TypeCode.getClass(): Class? = when (this.value) { +private fun Questionnaire.Item.TypeCode.getClass(): Class? = + when (this.value) { QuestionnaireItemTypeCode.Value.DATE -> Date::class.java QuestionnaireItemTypeCode.Value.BOOLEAN -> Boolean::class.java QuestionnaireItemTypeCode.Value.DECIMAL -> Decimal::class.java @@ -135,26 +132,31 @@ private fun Questionnaire.Item.TypeCode.getClass(): Class? = when ( QuestionnaireItemTypeCode.Value.DATE_TIME -> DateTime::class.java QuestionnaireItemTypeCode.Value.TIME -> Time::class.java QuestionnaireItemTypeCode.Value.STRING, QuestionnaireItemTypeCode.Value.TEXT -> - com.google.fhir.r4.core.String::class.java + com.google.fhir.r4.core.String::class.java QuestionnaireItemTypeCode.Value.URL -> Url::class.java else -> null -} + } /** - * The map from the `name`s to `expression`s in the [item extraction context extension](http://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-itemExtractionContext.html)s. + * The map from the `name`s to `expression`s in the + * [item extraction context extension](http://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-itemExtractionContext.html) + * s. */ private val Questionnaire.itemContextNameToExpressionMap: Map - get() { - return this.extensionList.filter { - it.url.value == ITEM_CONTEXT_EXTENSION_URL - }.map { - val expression = it.value.expression - expression.name.value to expression.expression.value - }.toMap() - } + get() { + return this.extensionList + .filter { it.url.value == ITEM_CONTEXT_EXTENSION_URL } + .map { + val expression = it.value.expression + expression.name.value to expression.expression.value + } + .toMap() + } /** - * See [Extension: item extraction context](http://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-itemExtractionContext.html). + * See + * [Extension: item extraction context](http://build.fhir.org/ig/HL7/sdc/StructureDefinition-sdc-questionnaire-itemExtractionContext.html) + * . */ private const val ITEM_CONTEXT_EXTENSION_URL: String = - "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext" + "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-itemContext" diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/DatePickerDialogFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/DatePickerDialogFragment.kt index a8548b87b5..075a150285 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/DatePickerDialogFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/DatePickerDialogFragment.kt @@ -27,38 +27,39 @@ import androidx.fragment.app.setFragmentResult import java.time.LocalDate internal class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener { - @SuppressLint("NewApi") // Suppress warnings for java.time APIs - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Use the current date as the default date in the picker - val today = LocalDate.now() + @SuppressLint("NewApi") // Suppress warnings for java.time APIs + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Use the current date as the default date in the picker + val today = LocalDate.now() - // Create a new instance of DatePickerDialog and return it - return DatePickerDialog( - requireContext(), - this, - today.year, - // month values are 1-12 in java.time but DatePickerDialog expects 0-11 - today.monthValue - 1, - today.dayOfMonth) - } + // Create a new instance of DatePickerDialog and return it + return DatePickerDialog( + requireContext(), + this, + today.year, + // month values are 1-12 in java.time but DatePickerDialog expects 0-11 + today.monthValue - 1, + today.dayOfMonth + ) + } - override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { - setFragmentResult( - RESULT_REQUEST_KEY, - bundleOf( - RESULT_BUNDLE_KEY_YEAR to year, - RESULT_BUNDLE_KEY_MONTH to month, - RESULT_BUNDLE_KEY_DAY_OF_MONTH to dayOfMonth - ) - ) - dismiss() - } + override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { + setFragmentResult( + RESULT_REQUEST_KEY, + bundleOf( + RESULT_BUNDLE_KEY_YEAR to year, + RESULT_BUNDLE_KEY_MONTH to month, + RESULT_BUNDLE_KEY_DAY_OF_MONTH to dayOfMonth + ) + ) + dismiss() + } - companion object { - const val TAG = "date-picker-fragment" - const val RESULT_REQUEST_KEY = "date-picker-request-key" - const val RESULT_BUNDLE_KEY_YEAR = "date-picker-bundle-key-year" - const val RESULT_BUNDLE_KEY_MONTH = "date-picker-bundle-key-month" - const val RESULT_BUNDLE_KEY_DAY_OF_MONTH = "date-picker-bundle-day-of-month" - } + companion object { + const val TAG = "date-picker-fragment" + const val RESULT_REQUEST_KEY = "date-picker-request-key" + const val RESULT_BUNDLE_KEY_YEAR = "date-picker-bundle-key-year" + const val RESULT_BUNDLE_KEY_MONTH = "date-picker-bundle-key-month" + const val RESULT_BUNDLE_KEY_DAY_OF_MONTH = "date-picker-bundle-day-of-month" + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactory.kt index 032188d642..322a804e6a 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxViewHolderFactory.kt @@ -22,33 +22,32 @@ import com.google.android.fhir.datacapture.R import com.google.fhir.r4.core.Boolean import com.google.fhir.r4.core.QuestionnaireResponse -internal object QuestionnaireItemCheckBoxViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_check_box_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var checkBox: CheckBox - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem +internal object QuestionnaireItemCheckBoxViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_check_box_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var checkBox: CheckBox + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - override fun init(itemView: View) { - checkBox = itemView.findViewById(R.id.check_box) - checkBox.setOnClickListener { - questionnaireItemViewItem.singleAnswerOrNull = - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = - QuestionnaireResponse.Item.Answer.ValueX.newBuilder().setBoolean( - Boolean.newBuilder().setValue(checkBox.isChecked).build() - ).build() - } - questionnaireItemViewItem.questionnaireResponseItemChangedCallback() - } - } - - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - checkBox.text = questionnaireItemViewItem.questionnaireItem.text.value - checkBox.isChecked = - questionnaireItemViewItem.singleAnswerOrNull?.value?.boolean?.value ?: false + override fun init(itemView: View) { + checkBox = itemView.findViewById(R.id.check_box) + checkBox.setOnClickListener { + questionnaireItemViewItem.singleAnswerOrNull = + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(checkBox.isChecked).build()) + .build() } + questionnaireItemViewItem.questionnaireResponseItemChangedCallback() } + } + + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + checkBox.text = questionnaireItemViewItem.questionnaireItem.text.value + checkBox.isChecked = + questionnaireItemViewItem.singleAnswerOrNull?.value?.boolean?.value ?: false + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt index 0665cd701f..90dcba87b8 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDatePickerViewHolderFactory.kt @@ -33,119 +33,114 @@ import java.time.LocalDate import java.time.ZoneId import java.time.format.DateTimeFormatter -internal object QuestionnaireItemDatePickerViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_date_picker_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var textDateQuestion: TextView - private lateinit var textInputEditText: TextInputEditText - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem +internal object QuestionnaireItemDatePickerViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_date_picker_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var textDateQuestion: TextView + private lateinit var textInputEditText: TextInputEditText + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - override fun init(itemView: View) { - textDateQuestion = itemView.findViewById(R.id.question) - textInputEditText = itemView.findViewById(R.id.textInputEditText) - // Disable direct text input to only allow input from the date picker dialog - textInputEditText.keyListener = null - textInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> - // Do not show the date picker dialog when losing focus. - if (!hasFocus) return@setOnFocusChangeListener + override fun init(itemView: View) { + textDateQuestion = itemView.findViewById(R.id.question) + textInputEditText = itemView.findViewById(R.id.textInputEditText) + // Disable direct text input to only allow input from the date picker dialog + textInputEditText.keyListener = null + textInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> + // Do not show the date picker dialog when losing focus. + if (!hasFocus) return@setOnFocusChangeListener - // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment - // and again in TextInputEditText during layout inflation. As a result, it is - // necessary to access the base context twice to retrieve the application object - // from the view's context. - val context = itemView.context.tryUnwrapContext()!! - context.supportFragmentManager.setFragmentResultListener( - DatePickerFragment.RESULT_REQUEST_KEY, - context, - object : FragmentResultListener { - // java.time APIs can be used with desugaring - @SuppressLint("NewApi") - override fun onFragmentResult(requestKey: String, result: Bundle) { - val year = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_YEAR) - val month = - result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_MONTH) - val dayOfMonth = - result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_DAY_OF_MONTH) - val zonedDateTime = LocalDate.of( - year, - // Month values are 1-12 in java.time but 0-11 in - // DatePickerDialog. - month + 1, - dayOfMonth - ).atStartOfDay().atZone(ZoneId.systemDefault()) - textInputEditText.setText( - zonedDateTime.format(LOCAL_DATE_FORMATTER) - ) - - val date = Date.newBuilder() - .setValueUs( - zonedDateTime.toEpochSecond() * - NUMBER_OF_MICROSECONDS_PER_SECOND - ) - .setPrecision(Date.Precision.DAY) - .setTimezone(ZoneId.systemDefault().id) - .build() - questionnaireItemViewItem.singleAnswerOrNull = - QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX - .newBuilder() - .setDate(date) - .build() - } - questionnaireItemViewItem.questionnaireResponseItemChangedCallback() - } - } - ) - DatePickerFragment().show( - context.supportFragmentManager, - DatePickerFragment.TAG + // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment + // and again in TextInputEditText during layout inflation. As a result, it is + // necessary to access the base context twice to retrieve the application object + // from the view's context. + val context = itemView.context.tryUnwrapContext()!! + context.supportFragmentManager.setFragmentResultListener( + DatePickerFragment.RESULT_REQUEST_KEY, + context, + object : FragmentResultListener { + // java.time APIs can be used with desugaring + @SuppressLint("NewApi") + override fun onFragmentResult(requestKey: String, result: Bundle) { + val year = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_YEAR) + val month = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_MONTH) + val dayOfMonth = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_DAY_OF_MONTH) + val zonedDateTime = + LocalDate.of( + year, + // Month values are 1-12 in java.time but 0-11 in + // DatePickerDialog. + month + 1, + dayOfMonth ) - // Clear focus so that the user can refocus to open the dialog - textDateQuestion.clearFocus() - } - } + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + textInputEditText.setText(zonedDateTime.format(LOCAL_DATE_FORMATTER)) - @SuppressLint("NewApi") // java.time APIs can be used due to desugaring - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - textDateQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value - textInputEditText.setText( - questionnaireItemViewItem.singleAnswerOrNull?.value?.date?.let { - Instant - .ofEpochMilli(it.valueUs / NUMBER_OF_MICROSECONDS_PER_MILLISECOND) - .atZone(ZoneId.systemDefault()) - }?.format(LOCAL_DATE_FORMATTER) ?: "" - ) + val date = + Date.newBuilder() + .setValueUs(zonedDateTime.toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND) + .setPrecision(Date.Precision.DAY) + .setTimezone(ZoneId.systemDefault().id) + .build() + questionnaireItemViewItem.singleAnswerOrNull = + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder().setDate(date).build() + } + questionnaireItemViewItem.questionnaireResponseItemChangedCallback() + } } + ) + DatePickerFragment().show(context.supportFragmentManager, DatePickerFragment.TAG) + // Clear focus so that the user can refocus to open the dialog + textDateQuestion.clearFocus() } + } - @SuppressLint("NewApi") // java.time APIs can be used due to desugaring - val LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE!! + @SuppressLint("NewApi") // java.time APIs can be used due to desugaring + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + textDateQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value + textInputEditText.setText( + questionnaireItemViewItem + .singleAnswerOrNull + ?.value + ?.date + ?.let { + Instant.ofEpochMilli(it.valueUs / NUMBER_OF_MICROSECONDS_PER_MILLISECOND) + .atZone(ZoneId.systemDefault()) + } + ?.format(LOCAL_DATE_FORMATTER) + ?: "" + ) + } + } + + @SuppressLint("NewApi") // java.time APIs can be used due to desugaring + val LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE!! } const val NUMBER_OF_MICROSECONDS_PER_SECOND = 1000000 const val NUMBER_OF_MICROSECONDS_PER_MILLISECOND = 1000 /** - * Returns the [AppCompatActivity] if there exists one wrapped inside [ContextThemeWrapper]s, or + * Returns the [AppCompatActivity] if there exists one wrapped inside [ContextThemeWrapper] s, or * `null` otherwise. * * This function is inspired by the function with the same name in `AppCompateDelegateImpl`. See * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java;l=1615 * - * TODO: find a more robust way to do this as it is not guaranteed that the activity is - * an AppCompatActivity. + * TODO: find a more robust way to do this as it is not guaranteed that the activity is an + * AppCompatActivity. */ internal fun Context.tryUnwrapContext(): AppCompatActivity? { - var context = this - while (true) { - when (context) { - is AppCompatActivity -> return context - is ContextThemeWrapper -> context = context.baseContext - else -> return null - } + var context = this + while (true) { + when (context) { + is AppCompatActivity -> return context + is ContextThemeWrapper -> context = context.baseContext + else -> return null } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt index 31aa7c2a7b..bc95d851a6 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDateTimePickerViewHolderFactory.kt @@ -32,156 +32,139 @@ import java.time.ZonedDateTime import java.time.format.DateTimeFormatter internal object QuestionnaireItemDateTimePickerViewHolderFactory : - QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_date_time_picker_view - ) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var textDateQuestion: TextView - private lateinit var dateInputEditText: TextInputEditText - private lateinit var textTimeQuestion: TextView - private lateinit var timeInputEditText: TextInputEditText - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_date_time_picker_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var textDateQuestion: TextView + private lateinit var dateInputEditText: TextInputEditText + private lateinit var textTimeQuestion: TextView + private lateinit var timeInputEditText: TextInputEditText + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - override fun init(itemView: View) { - textDateQuestion = itemView.findViewById(R.id.date_question) - dateInputEditText = itemView.findViewById(R.id.dateInputEditText) - // Disable direct text input to only allow input from the date picker dialog - dateInputEditText.keyListener = null - dateInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> - // Do not show the date picker dialog when losing focus. - if (!hasFocus) return@setOnFocusChangeListener + override fun init(itemView: View) { + textDateQuestion = itemView.findViewById(R.id.date_question) + dateInputEditText = itemView.findViewById(R.id.dateInputEditText) + // Disable direct text input to only allow input from the date picker dialog + dateInputEditText.keyListener = null + dateInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> + // Do not show the date picker dialog when losing focus. + if (!hasFocus) return@setOnFocusChangeListener - // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment - // and again in TextInputEditText during layout inflation. As a result, it is - // necessary to access the base context twice to retrieve the application object - // from the view's context. - val context = itemView.context.tryUnwrapContext()!! - context.supportFragmentManager.setFragmentResultListener( - DatePickerFragment.RESULT_REQUEST_KEY, - context, - object : FragmentResultListener { - // java.time APIs can be used with desugaring - @SuppressLint("NewApi") - override fun onFragmentResult(requestKey: String, result: Bundle) { - val year = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_YEAR) - val month = - result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_MONTH) - val dayOfMonth = - result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_DAY_OF_MONTH) - val zonedDateTime = LocalDate.of( - year, - // Month values are 1-12 in java.time but 0-11 in - // DatePickerDialog. - month + 1, - dayOfMonth - ).atStartOfDay().atZone(ZoneId.systemDefault()) - updateDateTimeInput(zonedDateTime) - updateDateTimeAnswer(zonedDateTime) - } - } + // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment + // and again in TextInputEditText during layout inflation. As a result, it is + // necessary to access the base context twice to retrieve the application object + // from the view's context. + val context = itemView.context.tryUnwrapContext()!! + context.supportFragmentManager.setFragmentResultListener( + DatePickerFragment.RESULT_REQUEST_KEY, + context, + object : FragmentResultListener { + // java.time APIs can be used with desugaring + @SuppressLint("NewApi") + override fun onFragmentResult(requestKey: String, result: Bundle) { + val year = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_YEAR) + val month = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_MONTH) + val dayOfMonth = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_DAY_OF_MONTH) + val zonedDateTime = + LocalDate.of( + year, + // Month values are 1-12 in java.time but 0-11 in + // DatePickerDialog. + month + 1, + dayOfMonth ) - DatePickerFragment().show( - context.supportFragmentManager, - DatePickerFragment.TAG - ) - // Clear focus so that the user can refocus to open the dialog - textDateQuestion.clearFocus() - } + .atStartOfDay() + .atZone(ZoneId.systemDefault()) + updateDateTimeInput(zonedDateTime) + updateDateTimeAnswer(zonedDateTime) + } + } + ) + DatePickerFragment().show(context.supportFragmentManager, DatePickerFragment.TAG) + // Clear focus so that the user can refocus to open the dialog + textDateQuestion.clearFocus() + } - textTimeQuestion = itemView.findViewById(R.id.time_question) - timeInputEditText = itemView.findViewById(R.id.timeInputEditText) - // Disable direct text input to only allow input from the time picker dialog - timeInputEditText.keyListener = null - timeInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> - // Do not show the date picker dialog when losing focus. - if (!hasFocus) return@setOnFocusChangeListener + textTimeQuestion = itemView.findViewById(R.id.time_question) + timeInputEditText = itemView.findViewById(R.id.timeInputEditText) + // Disable direct text input to only allow input from the time picker dialog + timeInputEditText.keyListener = null + timeInputEditText.setOnFocusChangeListener { _: View, hasFocus: Boolean -> + // Do not show the date picker dialog when losing focus. + if (!hasFocus) return@setOnFocusChangeListener - // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment - // and again in TextInputEditText during layout inflation. As a result, it is - // necessary to access the base context twice to retrieve the application object - // from the view's context. - val context = itemView.context.tryUnwrapContext()!! - context.supportFragmentManager.setFragmentResultListener( - TimePickerFragment.RESULT_REQUEST_KEY, - context, - object : FragmentResultListener { - // java.time APIs can be used with desugaring - @SuppressLint("NewApi") - override fun onFragmentResult(requestKey: String, result: Bundle) { - val hour = result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_HOUR) - val minute = - result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_MINUTE) - val zonedDateTime = Instant - .ofEpochMilli( - questionnaireItemViewItem.singleAnswerOrNull!! - .value.dateTime.millis - ) - .atZone(ZoneId.systemDefault()) - .withHour(hour) - .withMinute(minute) - updateDateTimeInput(zonedDateTime) - updateDateTimeAnswer(zonedDateTime) - } - } + // The application is wrapped in a ContextThemeWrapper in QuestionnaireFragment + // and again in TextInputEditText during layout inflation. As a result, it is + // necessary to access the base context twice to retrieve the application object + // from the view's context. + val context = itemView.context.tryUnwrapContext()!! + context.supportFragmentManager.setFragmentResultListener( + TimePickerFragment.RESULT_REQUEST_KEY, + context, + object : FragmentResultListener { + // java.time APIs can be used with desugaring + @SuppressLint("NewApi") + override fun onFragmentResult(requestKey: String, result: Bundle) { + val hour = result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_HOUR) + val minute = result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_MINUTE) + val zonedDateTime = + Instant.ofEpochMilli( + questionnaireItemViewItem.singleAnswerOrNull!!.value.dateTime.millis ) - TimePickerFragment().show( - context.supportFragmentManager, - TimePickerFragment.TAG - ) - // Clear focus so that the user can refocus to open the dialog - textTimeQuestion.clearFocus() - } + .atZone(ZoneId.systemDefault()) + .withHour(hour) + .withMinute(minute) + updateDateTimeInput(zonedDateTime) + updateDateTimeAnswer(zonedDateTime) + } } + ) + TimePickerFragment().show(context.supportFragmentManager, TimePickerFragment.TAG) + // Clear focus so that the user can refocus to open the dialog + textTimeQuestion.clearFocus() + } + } - @SuppressLint("NewApi") // java.time APIs can be used due to desugaring - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - textDateQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value - textTimeQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value - val dateTime = questionnaireItemViewItem.singleAnswerOrNull?.value?.dateTime - updateDateTimeInput( - dateTime?.let { - Instant - .ofEpochMilli(it.millis) - .atZone(ZoneId.systemDefault()) - } - ) - } + @SuppressLint("NewApi") // java.time APIs can be used due to desugaring + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + textDateQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value + textTimeQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value + val dateTime = questionnaireItemViewItem.singleAnswerOrNull?.value?.dateTime + updateDateTimeInput( + dateTime?.let { Instant.ofEpochMilli(it.millis).atZone(ZoneId.systemDefault()) } + ) + } - /** Update the date and time input fields in the UI. */ - fun updateDateTimeInput(zonedDateTime: ZonedDateTime?) { - timeInputEditText.isEnabled = zonedDateTime != null - dateInputEditText.setText(zonedDateTime?.format(LOCAL_DATE_FORMATTER) ?: "") - timeInputEditText.setText(zonedDateTime?.format(LOCAL_TIME_FORMATTER) ?: "") - } + /** Update the date and time input fields in the UI. */ + fun updateDateTimeInput(zonedDateTime: ZonedDateTime?) { + timeInputEditText.isEnabled = zonedDateTime != null + dateInputEditText.setText(zonedDateTime?.format(LOCAL_DATE_FORMATTER) ?: "") + timeInputEditText.setText(zonedDateTime?.format(LOCAL_TIME_FORMATTER) ?: "") + } - /** Updates the recorded answer. */ - fun updateDateTimeAnswer(zonedDateTime: ZonedDateTime) { - // Update answer - val dateTime = DateTime.newBuilder() - .setValueUs( - zonedDateTime.toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND - ) - .setTimezone(ZoneId.systemDefault().id) - .setPrecision(DateTime.Precision.SECOND) - .build() - questionnaireItemViewItem.singleAnswerOrNull = - QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX - .newBuilder() - .setDateTime(dateTime) - .build() - } - questionnaireItemViewItem.questionnaireResponseItemChangedCallback() - } - } + /** Updates the recorded answer. */ + fun updateDateTimeAnswer(zonedDateTime: ZonedDateTime) { + // Update answer + val dateTime = + DateTime.newBuilder() + .setValueUs(zonedDateTime.toEpochSecond() * NUMBER_OF_MICROSECONDS_PER_SECOND) + .setTimezone(ZoneId.systemDefault().id) + .setPrecision(DateTime.Precision.SECOND) + .build() + questionnaireItemViewItem.singleAnswerOrNull = + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder().setDateTime(dateTime).build() + } + questionnaireItemViewItem.questionnaireResponseItemChangedCallback() + } + } - @SuppressLint("NewApi") // java.time APIs can be used due to desugaring - val LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE!! - val LOCAL_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME!! + @SuppressLint("NewApi") // java.time APIs can be used due to desugaring + val LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE!! + val LOCAL_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_TIME!! } private val DateTime.millis - get() = valueUs / NUMBER_OF_MICROSECONDS_PER_MILLISECOND + get() = valueUs / NUMBER_OF_MICROSECONDS_PER_MILLISECOND diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt index d7afd72f58..2c0079a11c 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDisplayViewHolderFactory.kt @@ -20,24 +20,24 @@ import android.view.View import android.widget.TextView import com.google.android.fhir.datacapture.R -internal object QuestionnaireItemDisplayViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_display_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var textView: TextView +internal object QuestionnaireItemDisplayViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_display_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var textView: TextView - override fun init(itemView: View) { - textView = itemView.findViewById(R.id.text_view) - } + override fun init(itemView: View) { + textView = itemView.findViewById(R.id.text_view) + } - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - textView.text = questionnaireItemViewItem.questionnaireItem.text.value - textView.visibility = if (textView.text.isEmpty()) { - View.GONE - } else { - View.VISIBLE - } - } - } + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + textView.text = questionnaireItemViewItem.questionnaireItem.text.value + textView.visibility = + if (textView.text.isEmpty()) { + View.GONE + } else { + View.VISIBLE + } + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt index 8150715b42..4ab7de0f31 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemDropDownViewHolderFactory.kt @@ -27,56 +27,48 @@ import com.google.android.fhir.datacapture.displayString import com.google.android.fhir.datacapture.responseAnswerValueX import com.google.fhir.r4.core.QuestionnaireResponse -internal object QuestionnaireItemDropDownViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_drop_down_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var textView: TextView - private lateinit var autoCompleteTextView: AutoCompleteTextView - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - private lateinit var context: Context +internal object QuestionnaireItemDropDownViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_drop_down_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var textView: TextView + private lateinit var autoCompleteTextView: AutoCompleteTextView + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem + private lateinit var context: Context - override fun init(itemView: View) { - textView = itemView.findViewById(R.id.dropdown_question_title) - autoCompleteTextView = itemView.findViewById(R.id.auto_complete) - context = itemView.context - } + override fun init(itemView: View) { + textView = itemView.findViewById(R.id.dropdown_question_title) + autoCompleteTextView = itemView.findViewById(R.id.auto_complete) + context = itemView.context + } - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - textView.text = questionnaireItemViewItem.questionnaireItem.text.value - val answerOptionString = - this.questionnaireItemViewItem.questionnaireItem.answerOptionList.map { - it.displayString - } - val adapter = ArrayAdapter( - context, - R.layout.questionnaire_item_drop_down_list, - answerOptionString - ) - autoCompleteTextView.setText( - questionnaireItemViewItem.singleAnswerOrNull?.value?.coding?.display?.value - ?: "" - ) - autoCompleteTextView.setAdapter(adapter) - autoCompleteTextView.onItemClickListener = - object : AdapterView.OnItemClickListener { - override fun onItemClick( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long - ) { - questionnaireItemViewItem.singleAnswerOrNull = - QuestionnaireResponse.Item.Answer.newBuilder() - .setValue( - questionnaireItemViewItem - .questionnaireItem - .answerOptionList[position].responseAnswerValueX - ) - } - } + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + textView.text = questionnaireItemViewItem.questionnaireItem.text.value + val answerOptionString = + this.questionnaireItemViewItem.questionnaireItem.answerOptionList.map { it.displayString } + val adapter = + ArrayAdapter(context, R.layout.questionnaire_item_drop_down_list, answerOptionString) + autoCompleteTextView.setText( + questionnaireItemViewItem.singleAnswerOrNull?.value?.coding?.display?.value ?: "" + ) + autoCompleteTextView.setAdapter(adapter) + autoCompleteTextView.onItemClickListener = + object : AdapterView.OnItemClickListener { + override fun onItemClick( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + questionnaireItemViewItem.singleAnswerOrNull = + QuestionnaireResponse.Item.Answer.newBuilder() + .setValue( + questionnaireItemViewItem.questionnaireItem.answerOptionList[position] + .responseAnswerValueX + ) } - } + } + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactory.kt index 42a0027f6c..46b2907997 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextDecimalViewHolderFactory.kt @@ -21,25 +21,26 @@ import com.google.fhir.r4.core.Decimal import com.google.fhir.r4.core.QuestionnaireResponse internal object QuestionnaireItemEditTextDecimalViewHolderFactory : - QuestionnaireItemEditTextViewHolderFactory() { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemEditTextViewHolderDelegate( - InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, - isSingleLine = true - ) { - override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { - return text.toDoubleOrNull()?.let { - QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setDecimal(Decimal.newBuilder().setValue(it.toString()).build()) - .build() - } - } - } - - override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { - return answer?.value?.decimal?.value?.toString() ?: "" - } + QuestionnaireItemEditTextViewHolderFactory() { + override fun getQuestionnaireItemViewHolderDelegate() = + object : + QuestionnaireItemEditTextViewHolderDelegate( + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, + isSingleLine = true + ) { + override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { + return text.toDoubleOrNull()?.let { + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setDecimal(Decimal.newBuilder().setValue(it.toString()).build()) + .build() + } } + } + + override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { + return answer?.value?.decimal?.value?.toString() ?: "" + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactory.kt index 4a4571d55a..5eff5acc5d 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextIntegerViewHolderFactory.kt @@ -21,25 +21,26 @@ import com.google.fhir.r4.core.Integer import com.google.fhir.r4.core.QuestionnaireResponse internal object QuestionnaireItemEditTextIntegerViewHolderFactory : - QuestionnaireItemEditTextViewHolderFactory() { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemEditTextViewHolderDelegate( - InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, - true - ) { - override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { - return text.toIntOrNull()?.let { - QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setInteger(Integer.newBuilder().setValue(it).build()) - .build() - } - } - } - - override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { - return answer?.value?.integer?.value?.toString() ?: "" - } + QuestionnaireItemEditTextViewHolderFactory() { + override fun getQuestionnaireItemViewHolderDelegate() = + object : + QuestionnaireItemEditTextViewHolderDelegate( + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, + true + ) { + override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { + return text.toIntOrNull()?.let { + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setInteger(Integer.newBuilder().setValue(it).build()) + .build() + } } + } + + override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { + return answer?.value?.integer?.value?.toString() ?: "" + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactory.kt index 38a5479d6b..cc28d0f6d0 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextMultiLineViewHolderFactory.kt @@ -17,7 +17,7 @@ package com.google.android.fhir.datacapture.views internal object QuestionnaireItemEditTextMultiLineViewHolderFactory : - QuestionnaireItemEditTextViewHolderFactory() { - override fun getQuestionnaireItemViewHolderDelegate() = - QuestionnaireItemEditTextStringViewHolderDelegate(false) + QuestionnaireItemEditTextViewHolderFactory() { + override fun getQuestionnaireItemViewHolderDelegate() = + QuestionnaireItemEditTextStringViewHolderDelegate(false) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt index 97c26f1cdd..c0a24db8d6 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextQuantityViewHolderFactory.kt @@ -22,32 +22,33 @@ import com.google.fhir.r4.core.Quantity import com.google.fhir.r4.core.QuestionnaireResponse /** - * Inherits from QuestionnaireItemEditTextViewHolderFactory as only the numeric part of the quantity is being handled right now. - * Will use a separate layout to handle the unit in the quantity. + * Inherits from QuestionnaireItemEditTextViewHolderFactory as only the numeric part of the quantity + * is being handled right now. Will use a separate layout to handle the unit in the quantity. */ internal object QuestionnaireItemEditTextQuantityViewHolderFactory : - QuestionnaireItemEditTextViewHolderFactory() { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemEditTextViewHolderDelegate( - InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, - isSingleLine = true - ) { - override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { - return text.toDoubleOrNull()?.let { - QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setQuantity( - Quantity.newBuilder().setValue( - Decimal.newBuilder().setValue(it.toString()).build() - ) - ).build() - } - } - } - - override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { - return answer?.value?.quantity?.value?.value ?: "" - } + QuestionnaireItemEditTextViewHolderFactory() { + override fun getQuestionnaireItemViewHolderDelegate() = + object : + QuestionnaireItemEditTextViewHolderDelegate( + InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_SIGNED, + isSingleLine = true + ) { + override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { + return text.toDoubleOrNull()?.let { + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setQuantity( + Quantity.newBuilder() + .setValue(Decimal.newBuilder().setValue(it.toString()).build()) + ) + .build() + } } + } + + override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { + return answer?.value?.quantity?.value?.value ?: "" + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactory.kt index 9d8cdb6897..2509e5d71e 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextSingleLineViewHolderFactory.kt @@ -17,7 +17,7 @@ package com.google.android.fhir.datacapture.views internal object QuestionnaireItemEditTextSingleLineViewHolderFactory : - QuestionnaireItemEditTextViewHolderFactory() { - override fun getQuestionnaireItemViewHolderDelegate() = - QuestionnaireItemEditTextStringViewHolderDelegate(true) + QuestionnaireItemEditTextViewHolderFactory() { + override fun getQuestionnaireItemViewHolderDelegate() = + QuestionnaireItemEditTextStringViewHolderDelegate(true) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextStringViewHolderDelegate.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextStringViewHolderDelegate.kt index d964ee3621..42b16628ae 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextStringViewHolderDelegate.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextStringViewHolderDelegate.kt @@ -26,29 +26,24 @@ import com.google.fhir.r4.core.QuestionnaireResponse * * Any `ViewHolder` containing a `EditText` view that collects text data should use this class. */ -internal class QuestionnaireItemEditTextStringViewHolderDelegate( - isSingleLine: Boolean -) : QuestionnaireItemEditTextViewHolderDelegate( - InputType.TYPE_CLASS_TEXT, - isSingleLine -) { - override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { - return text.let { - if (it.isEmpty()) { - null - } else { - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = - QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setStringValue( - com.google.fhir.r4.core.String.newBuilder().setValue(it).build() - ).build() - } - } +internal class QuestionnaireItemEditTextStringViewHolderDelegate(isSingleLine: Boolean) : + QuestionnaireItemEditTextViewHolderDelegate(InputType.TYPE_CLASS_TEXT, isSingleLine) { + override fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? { + return text.let { + if (it.isEmpty()) { + null + } else { + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setStringValue(com.google.fhir.r4.core.String.newBuilder().setValue(it).build()) + .build() } + } } + } - override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { - return answer?.value?.stringValue?.value ?: "" - } + override fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String { + return answer?.value?.stringValue?.value ?: "" + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt index 5d6e9d73fe..0f1e1d565f 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemEditTextViewHolderFactory.kt @@ -25,44 +25,42 @@ import com.google.android.material.textfield.TextInputEditText import com.google.fhir.r4.core.QuestionnaireResponse internal abstract class QuestionnaireItemEditTextViewHolderFactory : - QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_edit_text_view - ) { - abstract override fun getQuestionnaireItemViewHolderDelegate(): - QuestionnaireItemEditTextViewHolderDelegate + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_edit_text_view) { + abstract override fun getQuestionnaireItemViewHolderDelegate(): + QuestionnaireItemEditTextViewHolderDelegate } internal abstract class QuestionnaireItemEditTextViewHolderDelegate( - private val rawInputType: Int, - private val isSingleLine: Boolean + private val rawInputType: Int, + private val isSingleLine: Boolean ) : QuestionnaireItemViewHolderDelegate { - private lateinit var textQuestion: TextView - private lateinit var textInputEditText: TextInputEditText - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem + private lateinit var textQuestion: TextView + private lateinit var textInputEditText: TextInputEditText + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - override fun init(itemView: View) { - textQuestion = itemView.findViewById(R.id.question) - textInputEditText = itemView.findViewById(R.id.textInputEditText) - textInputEditText.setRawInputType(rawInputType) - textInputEditText.isSingleLine = isSingleLine - textInputEditText.doAfterTextChanged { editable: Editable? -> - questionnaireItemViewItem.singleAnswerOrNull = getValue(editable.toString()) - questionnaireItemViewItem.questionnaireResponseItemChangedCallback() - } + override fun init(itemView: View) { + textQuestion = itemView.findViewById(R.id.question) + textInputEditText = itemView.findViewById(R.id.textInputEditText) + textInputEditText.setRawInputType(rawInputType) + textInputEditText.isSingleLine = isSingleLine + textInputEditText.doAfterTextChanged { editable: Editable? -> + questionnaireItemViewItem.singleAnswerOrNull = getValue(editable.toString()) + questionnaireItemViewItem.questionnaireResponseItemChangedCallback() } + } - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - textQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value - textInputEditText.setText(getText(questionnaireItemViewItem.singleAnswerOrNull)) - } + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + textQuestion.text = questionnaireItemViewItem.questionnaireItem.text.value + textInputEditText.setText(getText(questionnaireItemViewItem.singleAnswerOrNull)) + } - /** Returns the answer that should be recorded given the text input by the user. */ - abstract fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? + /** Returns the answer that should be recorded given the text input by the user. */ + abstract fun getValue(text: String): QuestionnaireResponse.Item.Answer.Builder? - /** - * Returns the text that should be displayed in the [TextInputEditText] from the existing - * answer to the question (may be input by the user or previously recorded). - */ - abstract fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String + /** + * Returns the text that should be displayed in the [TextInputEditText] from the existing answer + * to the question (may be input by the user or previously recorded). + */ + abstract fun getText(answer: QuestionnaireResponse.Item.Answer.Builder?): String } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt index cc57df8630..71ccbdd859 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemGroupViewHolderFactory.kt @@ -20,24 +20,24 @@ import android.view.View import android.widget.TextView import com.google.android.fhir.datacapture.R -internal object QuestionnaireItemGroupViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_group_header_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var groupHeader: TextView +internal object QuestionnaireItemGroupViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_group_header_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var groupHeader: TextView - override fun init(itemView: View) { - groupHeader = itemView.findViewById(R.id.group_header) - } + override fun init(itemView: View) { + groupHeader = itemView.findViewById(R.id.group_header) + } - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - groupHeader.text = questionnaireItemViewItem.questionnaireItem.text.value - groupHeader.visibility = if (groupHeader.text.isEmpty()) { - View.GONE - } else { - View.VISIBLE - } - } - } + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + groupHeader.text = questionnaireItemViewItem.questionnaireItem.text.value + groupHeader.visibility = + if (groupHeader.text.isEmpty()) { + View.GONE + } else { + View.VISIBLE + } + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactory.kt index 1186781700..ee1cd4b4ca 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemRadioGroupViewHolderFactory.kt @@ -26,50 +26,52 @@ import com.google.android.fhir.datacapture.displayString import com.google.android.fhir.datacapture.responseAnswerValueX import com.google.fhir.r4.core.QuestionnaireResponse -internal object QuestionnaireItemRadioGroupViewHolderFactory : QuestionnaireItemViewHolderFactory( - R.layout.questionnaire_item_radio_group_view -) { - override fun getQuestionnaireItemViewHolderDelegate() = - object : QuestionnaireItemViewHolderDelegate { - private lateinit var radioHeader: TextView - private lateinit var radioGroup: RadioGroup - private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem +internal object QuestionnaireItemRadioGroupViewHolderFactory : + QuestionnaireItemViewHolderFactory(R.layout.questionnaire_item_radio_group_view) { + override fun getQuestionnaireItemViewHolderDelegate() = + object : QuestionnaireItemViewHolderDelegate { + private lateinit var radioHeader: TextView + private lateinit var radioGroup: RadioGroup + private lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem - override fun init(itemView: View) { - radioGroup = itemView.findViewById(R.id.radio_group) - radioHeader = itemView.findViewById(R.id.radio_header) - } + override fun init(itemView: View) { + radioGroup = itemView.findViewById(R.id.radio_group) + radioHeader = itemView.findViewById(R.id.radio_header) + } - override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - this.questionnaireItemViewItem = questionnaireItemViewItem - val questionnaireItem = questionnaireItemViewItem.questionnaireItem - val questionnaireResponseItemBuilder = - questionnaireItemViewItem.questionnaireResponseItemBuilder - val answer = - questionnaireResponseItemBuilder.answerList.singleOrNull()?.value?.coding - radioHeader.text = questionnaireItem.text.value - radioGroup.removeAllViews() - var index = 0 - questionnaireItem.answerOptionList.forEach { - radioGroup.addView(RadioButton(radioGroup.context).apply { - id = index++ // Use the answer option index as radio button ID - text = it.displayString - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - this.isChecked = it.value.coding == answer - }) - } - radioGroup.setOnCheckedChangeListener { _, checkedId -> - questionnaireResponseItemBuilder.clearAnswer().addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = questionnaireItem - .answerOptionList[checkedId].responseAnswerValueX - } - ) - questionnaireItemViewItem.questionnaireResponseItemChangedCallback() - } + override fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + this.questionnaireItemViewItem = questionnaireItemViewItem + val questionnaireItem = questionnaireItemViewItem.questionnaireItem + val questionnaireResponseItemBuilder = + questionnaireItemViewItem.questionnaireResponseItemBuilder + val answer = questionnaireResponseItemBuilder.answerList.singleOrNull()?.value?.coding + radioHeader.text = questionnaireItem.text.value + radioGroup.removeAllViews() + var index = 0 + questionnaireItem.answerOptionList.forEach { + radioGroup.addView( + RadioButton(radioGroup.context).apply { + id = index++ // Use the answer option index as radio button ID + text = it.displayString + layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + this.isChecked = it.value.coding == answer } + ) + } + radioGroup.setOnCheckedChangeListener { _, checkedId -> + questionnaireResponseItemBuilder + .clearAnswer() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = questionnaireItem.answerOptionList[checkedId].responseAnswerValueX + } + ) + questionnaireItemViewItem.questionnaireResponseItemChangedCallback() } + } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt index c2767db68d..9ef6f25436 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewHolderFactory.kt @@ -28,18 +28,18 @@ import androidx.recyclerview.widget.RecyclerView * @param resId the layout resource for the view */ internal abstract class QuestionnaireItemViewHolderFactory(@LayoutRes val resId: Int) { - internal fun create(parent: ViewGroup): QuestionnaireItemViewHolder { - return QuestionnaireItemViewHolder( - LayoutInflater.from(parent.context).inflate(resId, parent, false), - getQuestionnaireItemViewHolderDelegate() - ) - } + internal fun create(parent: ViewGroup): QuestionnaireItemViewHolder { + return QuestionnaireItemViewHolder( + LayoutInflater.from(parent.context).inflate(resId, parent, false), + getQuestionnaireItemViewHolderDelegate() + ) + } - /** - * Returns a [QuestionnaireItemViewHolderDelegate] that handles the initialization of views and - * binding of items in [RecyclerView]. - */ - abstract fun getQuestionnaireItemViewHolderDelegate(): QuestionnaireItemViewHolderDelegate + /** + * Returns a [QuestionnaireItemViewHolderDelegate] that handles the initialization of views and + * binding of items in [RecyclerView]. + */ + abstract fun getQuestionnaireItemViewHolderDelegate(): QuestionnaireItemViewHolderDelegate } /** @@ -48,16 +48,16 @@ internal abstract class QuestionnaireItemViewHolderFactory(@LayoutRes val resId: * This is used by [QuestionnaireItemAdapter] to initialize views and bind items in [RecyclerView]. */ internal class QuestionnaireItemViewHolder( - itemView: View, - private val delegate: QuestionnaireItemViewHolderDelegate + itemView: View, + private val delegate: QuestionnaireItemViewHolderDelegate ) : RecyclerView.ViewHolder(itemView) { - init { - delegate.init(itemView) - } + init { + delegate.init(itemView) + } - fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { - delegate.bind(questionnaireItemViewItem) - } + fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) { + delegate.bind(questionnaireItemViewItem) + } } /** @@ -66,18 +66,17 @@ internal class QuestionnaireItemViewHolder( * This interface provides an abstraction of the operations that need to be implemented for a type * of view in the questionnaire. * - * There is a 1:1 relationship between this and - * [QuestionnaireItemViewHolder]. In other words, there is a unique - * [QuestionnaireItemViewHolderDelegate] for each [QuestionnaireItemViewHolder]. This is critical - * for the correctness of the recycler view. + * There is a 1:1 relationship between this and [QuestionnaireItemViewHolder]. In other words, there + * is a unique [QuestionnaireItemViewHolderDelegate] for each [QuestionnaireItemViewHolder]. This is + * critical for the correctness of the recycler view. */ internal interface QuestionnaireItemViewHolderDelegate { - /** - * Initializes the view in [QuestionnaireItemViewHolder]. Any listeners to record user input - * should be set in this function. - */ - fun init(itemView: View) + /** + * Initializes the view in [QuestionnaireItemViewHolder]. Any listeners to record user input + * should be set in this function. + */ + fun init(itemView: View) - /** Binds a [QuestionnaireItemViewItem] to the view. */ - fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) + /** Binds a [QuestionnaireItemViewItem] to the view. */ + fun bind(questionnaireItemViewItem: QuestionnaireItemViewItem) } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItem.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItem.kt index da8486572b..84ede8afa2 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItem.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItem.kt @@ -21,10 +21,11 @@ import com.google.fhir.r4.core.Questionnaire import com.google.fhir.r4.core.QuestionnaireResponse /** - * Item for [QuestionnaireItemViewHolder] in [RecyclerView] containing [Questionnaire.Item] (the - * question) and [QuestionnaireResponse.Item] (the answer). + * Item for [QuestionnaireItemViewHolder] in [RecyclerView] containing + * [Questionnaire.Item](the + * question) and [QuestionnaireResponse.Item](the answer). * - * [Questionnaire.Item] (the question) and [QuestionnaireResponse.Item] (the answer) are used to + * [Questionnaire.Item](the question) and [QuestionnaireResponse.Item](the answer) are used to * create the right type of view (e.g. a CheckBox for a yes/no question) and populate the view with * the right information (e.g text for the CheckBox and initial yes/no answer for the CheckBox). * @@ -33,20 +34,18 @@ import com.google.fhir.r4.core.QuestionnaireResponse * updated */ internal data class QuestionnaireItemViewItem( - val questionnaireItem: Questionnaire.Item, - val questionnaireResponseItemBuilder: QuestionnaireResponse.Item.Builder, - val questionnaireResponseItemChangedCallback: () -> Unit + val questionnaireItem: Questionnaire.Item, + val questionnaireResponseItemBuilder: QuestionnaireResponse.Item.Builder, + val questionnaireResponseItemChangedCallback: () -> Unit ) { - /** - * The single answer to the [QuestionnaireResponse.Item], or `null` if there is none or more - * than one answer. - */ - var singleAnswerOrNull - get() = questionnaireResponseItemBuilder.answerBuilderList.singleOrNull() - set(value) { - questionnaireResponseItemBuilder.clearAnswer() - value?.let { - questionnaireResponseItemBuilder.addAnswer(it) - } - } + /** + * The single answer to the [QuestionnaireResponse.Item], or `null` if there is none or more than + * one answer. + */ + var singleAnswerOrNull + get() = questionnaireResponseItemBuilder.answerBuilderList.singleOrNull() + set(value) { + questionnaireResponseItemBuilder.clearAnswer() + value?.let { questionnaireResponseItemBuilder.addAnswer(it) } + } } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/TimePickerDialogFragment.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/TimePickerDialogFragment.kt index 82505cb162..c73eb2ddd6 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/TimePickerDialogFragment.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/TimePickerDialogFragment.kt @@ -27,28 +27,25 @@ import androidx.fragment.app.setFragmentResult import java.time.LocalTime internal class TimePickerFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener { - @SuppressLint("NewApi") // Suppress warnings for java.time APIs - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - // Use the current time as the default time in the picker - val now = LocalTime.now() - return TimePickerDialog(requireContext(), this, now.hour, now.minute, false) - } + @SuppressLint("NewApi") // Suppress warnings for java.time APIs + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + // Use the current time as the default time in the picker + val now = LocalTime.now() + return TimePickerDialog(requireContext(), this, now.hour, now.minute, false) + } - override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { - setFragmentResult( - RESULT_REQUEST_KEY, - bundleOf( - RESULT_BUNDLE_KEY_HOUR to hourOfDay, - RESULT_BUNDLE_KEY_MINUTE to minute - ) - ) - dismiss() - } + override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { + setFragmentResult( + RESULT_REQUEST_KEY, + bundleOf(RESULT_BUNDLE_KEY_HOUR to hourOfDay, RESULT_BUNDLE_KEY_MINUTE to minute) + ) + dismiss() + } - companion object { - const val TAG = "time-picker-fragment" - const val RESULT_REQUEST_KEY = "time-picker-request-key" - const val RESULT_BUNDLE_KEY_HOUR = "time-picker-bundle-key-hour" - const val RESULT_BUNDLE_KEY_MINUTE = "time-picker-bundle-key-minute" - } + companion object { + const val TAG = "time-picker-fragment" + const val RESULT_REQUEST_KEY = "time-picker-request-key" + const val RESULT_BUNDLE_KEY_HOUR = "time-picker-bundle-key-hour" + const val RESULT_BUNDLE_KEY_MINUTE = "time-picker-bundle-key-minute" + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt index 93367f6ce5..12d22211a6 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt @@ -33,101 +33,84 @@ import org.robolectric.annotation.Config @Config(sdk = [Build.VERSION_CODES.P]) class MoreAnswerOptionsTest { - @Test - fun getDisplayString_choiceItemType_answerOptionShouldReturnValueCodingDisplayValue() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ) - - ).build() + @Test + fun getDisplayString_choiceItemType_answerOptionShouldReturnValueCodingDisplayValue() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) + ) + .build() - assertThat(answerOption.displayString).isEqualTo("Test Code") - } + assertThat(answerOption.displayString).isEqualTo("Test Code") + } - @Test - fun getDisplayString_choiceItemType_answerOptionShouldReturnValueCodingCodeValue() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - ) - ).build() + @Test + fun getDisplayString_choiceItemType_answerOptionShouldReturnValueCodingCodeValue() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding(Coding.newBuilder().setCode(Code.newBuilder().setValue("test-code"))) + ) + .build() - assertThat(answerOption.displayString).isEqualTo("test-code") - } + assertThat(answerOption.displayString).isEqualTo("test-code") + } - @Test - fun getDisplayString_choiceItemType_shouldThrowExceptionForIllegalAnswerOptionValueX() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setStringValue( - String.newBuilder() - .setValue("test") - ) - ).build() + @Test + fun getDisplayString_choiceItemType_shouldThrowExceptionForIllegalAnswerOptionValueX() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setStringValue(String.newBuilder().setValue("test")) + ) + .build() - assertFailsWith { - answerOption.displayString - } - } + assertFailsWith { answerOption.displayString } + } - @Test - fun getResponseAnswerValueX_ShouldReturnAnswerValueCoding() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ) - ).build() - val answerValueX = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + @Test + fun getResponseAnswerValueX_ShouldReturnAnswerValueCoding() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder().setValue("Test Code")) - ).build() + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) + ) + .build() + val answerValueX = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) + .build() - assertThat(answerOption.responseAnswerValueX).isEqualTo(answerValueX) - } + assertThat(answerOption.responseAnswerValueX).isEqualTo(answerValueX) + } - @Test - fun getResponseAnswerValueX_choiceItemType_shouldThrowExceptionForIllegalAnswerOptionValueX() { - val answerOption = Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setStringValue( - String.newBuilder() - .setValue("test") - ) - ).build() + @Test + fun getResponseAnswerValueX_choiceItemType_shouldThrowExceptionForIllegalAnswerOptionValueX() { + val answerOption = + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setStringValue(String.newBuilder().setValue("test")) + ) + .build() - assertFailsWith { - answerOption.responseAnswerValueX - } - } + assertFailsWith { answerOption.responseAnswerValueX } + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensionsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensionsTest.kt index fe634fb07d..e6b365b86c 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensionsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreQuestionnaireItemExtensionsTest.kt @@ -35,171 +35,119 @@ import org.robolectric.annotation.Config @Config(sdk = [Build.VERSION_CODES.P]) class MoreQuestionnaireItemExtensionsTest { - @Test - fun itemControl_shouldReturnItemControlCodeDropDown() { - - val questionnaireItem = Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_URL) - ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue(ITEM_CONTROL_DROP_DOWN) - ) - .setDisplay( - String.newBuilder() - .setValue("Drop Down") - ) - .setSystem( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_SYSTEM) - ) - ) - ) - + @Test + fun itemControl_shouldReturnItemControlCodeDropDown() { + + val questionnaireItem = + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder().setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_URL)) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue(ITEM_CONTROL_DROP_DOWN)) + .setDisplay(String.newBuilder().setValue("Drop Down")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) + ) ) - .build() - - assertThat(questionnaireItem.itemControl).isEqualTo(ITEM_CONTROL_DROP_DOWN) - } - - @Test - fun itemControl_shouldReturnItemControlCodeRadioButton() { - - val questionnaireItem = Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_URL) + ) + .build() + + assertThat(questionnaireItem.itemControl).isEqualTo(ITEM_CONTROL_DROP_DOWN) + } + + @Test + fun itemControl_shouldReturnItemControlCodeRadioButton() { + + val questionnaireItem = + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder().setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_URL)) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue(ITEM_CONTROL_RADIO_BUTTON)) + .setDisplay(String.newBuilder().setValue("Radio Group")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue(ITEM_CONTROL_RADIO_BUTTON) - ) - .setDisplay( - String.newBuilder() - .setValue("Radio Group") - ) - .setSystem( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_SYSTEM) - ) - ) - ) - - ) - ) - .build() - - assertThat(questionnaireItem.itemControl).isEqualTo(ITEM_CONTROL_RADIO_BUTTON) - } - - @Test - fun itemControl_wrongExtensionUrl_shouldReturnNull() { - - val questionnaireItem = Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) ) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue("null-test") - ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue(ITEM_CONTROL_DROP_DOWN) - ) - .setDisplay( - String.newBuilder() - .setValue("Drop Down") - ) - .setSystem( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_SYSTEM) - ) - ) - ) - + ) + .build() + + assertThat(questionnaireItem.itemControl).isEqualTo(ITEM_CONTROL_RADIO_BUTTON) + } + + @Test + fun itemControl_wrongExtensionUrl_shouldReturnNull() { + + val questionnaireItem = + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder().setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue("null-test")) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue(ITEM_CONTROL_DROP_DOWN)) + .setDisplay(String.newBuilder().setValue("Drop Down")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) + ) ) - .build() - - assertThat(questionnaireItem.itemControl).isNull() - } - - @Test - fun itemControl_wrongExtensionCoding_shouldReturnNull() { - - val questionnaireItem = Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_URL) - ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("null-test") - ) - .setDisplay( - String.newBuilder() - .setValue("Drop Down") - ) - .setSystem( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_SYSTEM) - ) - ) - ) - + ) + .build() + + assertThat(questionnaireItem.itemControl).isNull() + } + + @Test + fun itemControl_wrongExtensionCoding_shouldReturnNull() { + + val questionnaireItem = + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder().setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_URL)) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("null-test")) + .setDisplay(String.newBuilder().setValue("Drop Down")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) + ) ) - .build() + ) + .build() - assertThat(questionnaireItem.itemControl).isNull() - } + assertThat(questionnaireItem.itemControl).isNull() + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt index 3e001d4ef4..8e74b9f580 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemAdapterTest.kt @@ -36,463 +36,416 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class QuestionnaireItemAdapterTest { - @Test - fun getItemViewType_groupItemType_shouldReturnGroupViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.GROUP) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_groupItemType_shouldReturnGroupViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.GROUP) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.GROUP.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.GROUP.value) + } - @Test - fun getItemViewType_booleanItemType_shouldReturnBooleanViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_booleanItemType_shouldReturnBooleanViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.CHECK_BOX.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.CHECK_BOX.value) + } - @Test - fun getItemViewType_dateItemType_shouldReturnDatePickerViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.DATE) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_dateItemType_shouldReturnDatePickerViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.DATE) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.DATE_PICKER.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.DATE_PICKER.value) + } - @Test - fun getItemViewType_dateTimeItemType_shouldReturnDateTimePickerViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.DATE_TIME) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_dateTimeItemType_shouldReturnDateTimePickerViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.DATE_TIME) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.DATE_TIME_PICKER.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.DATE_TIME_PICKER.value) + } - @Test - fun getItemViewType_stringItemType_shouldReturnEditTextViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.STRING) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_stringItemType_shouldReturnEditTextViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.STRING) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.EDIT_TEXT_SINGLE_LINE.value) + } - @Test - fun getItemViewType_textItemType_shouldReturnEditTextViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.TEXT) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_textItemType_shouldReturnEditTextViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.TEXT) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.EDIT_TEXT_MULTI_LINE.value) + } - @Test - fun getItemViewType_integerItemType_shouldReturnEditTextIntegerViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.INTEGER) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_integerItemType_shouldReturnEditTextIntegerViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.INTEGER) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.EDIT_TEXT_INTEGER.value) + } - @Test - fun getItemViewType_decimalItemType_shouldReturnEditTextDecimalViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.DECIMAL) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_decimalItemType_shouldReturnEditTextDecimalViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.DECIMAL) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.EDIT_TEXT_DECIMAL.value) + } - @Test - fun getItemViewType_choiceItemType_lessAnswerOptions_shouldReturnRadioGroupViewHolderType() { - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + @Test + fun getItemViewType_choiceItemType_lessAnswerOptions_shouldReturnRadioGroupViewHolderType() { + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.CHOICE) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)).isEqualTo( - QuestionnaireItemViewHolderType.RADIO_GROUP.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.RADIO_GROUP.value) + } - @Test - fun getItemViewType_choiceItemType_moreAnswerOptions_shouldReturnDropDownViewHolderType() { - val answerOptions = Iterable { - iterator { - repeat(QuestionnaireItemAdapter.MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN) { - yield( - Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder() - .setValue("Test Code") - ) - ) - ) - .build() - ) - } - } + @Test + fun getItemViewType_choiceItemType_moreAnswerOptions_shouldReturnDropDownViewHolderType() { + val answerOptions = Iterable { + iterator { + repeat(QuestionnaireItemAdapter.MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN) { + yield( + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) + ) + .build() + ) } - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addAllAnswerOption(answerOptions) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + } + } + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.CHOICE) ) - ) + .addAllAnswerOption(answerOptions) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)) - .isEqualTo(QuestionnaireItemViewHolderType.DROP_DOWN.value) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.DROP_DOWN.value) + } - @Test - fun getItemViewType_choiceItemType_itemControlExtensionWithRadioButton_shouldReturnRadioGroupViewHolder() { // ktlint-disable max-line-length - val answerOptions = Iterable { - iterator { - repeat(QuestionnaireItemAdapter.MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN) { - yield( - Questionnaire.Item.AnswerOption.newBuilder() - .setValue( - Questionnaire.Item.AnswerOption.ValueX.newBuilder() - .setCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue("test-code") - ) - .setDisplay( - String.newBuilder() - .setValue("Test Code") - ) - ) - ) - .build() - ) - } - } + @Test + fun getItemViewType_choiceItemType_itemControlExtensionWithRadioButton_shouldReturnRadioGroupViewHolder() { // ktlint-disable max-line-length + val answerOptions = Iterable { + iterator { + repeat(QuestionnaireItemAdapter.MINIMUM_NUMBER_OF_ANSWER_OPTIONS_FOR_DROP_DOWN) { + yield( + Questionnaire.Item.AnswerOption.newBuilder() + .setValue( + Questionnaire.Item.AnswerOption.ValueX.newBuilder() + .setCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue("test-code")) + .setDisplay(String.newBuilder().setValue("Test Code")) + ) + ) + .build() + ) } - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addAllAnswerOption(answerOptions) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_URL) - ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue(ITEM_CONTROL_RADIO_BUTTON) - ) - .setDisplay( - String.newBuilder() - .setValue("Radio Button") - ) - .setSystem( - Uri.newBuilder() - .setValue( - EXTENSION_ITEM_CONTROL_SYSTEM - ) - ) - ) - ) - ) + } + } + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addAllAnswerOption(answerOptions) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_URL)) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue(ITEM_CONTROL_RADIO_BUTTON)) + .setDisplay(String.newBuilder().setValue("Radio Button")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + ) + ) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)) - .isEqualTo(QuestionnaireItemViewHolderType.RADIO_GROUP.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.RADIO_GROUP.value) + } - @Test - fun getItemViewType_choiceItemType_itemControlExtensionWithDropDown_shouldReturnDropDownViewHolderType() { // ktlint-disable max-line-length - val questionnaireItemAdapter = QuestionnaireItemAdapter() - questionnaireItemAdapter.submitList( - listOf( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.CHOICE) - ) - .addExtension( - Extension.newBuilder() - .setUrl( - Uri.newBuilder() - .setValue(EXTENSION_ITEM_CONTROL_URL) - ) - .setValue( - Extension.ValueX.newBuilder() - .setCodeableConcept( - CodeableConcept.newBuilder() - .addCoding( - Coding.newBuilder() - .setCode( - Code.newBuilder() - .setValue(ITEM_CONTROL_DROP_DOWN) - ) - .setDisplay( - String.newBuilder() - .setValue("Drop Down") - ) - .setSystem( - Uri.newBuilder() - .setValue( - EXTENSION_ITEM_CONTROL_SYSTEM - ) - ) - ) - ) - ) + @Test + fun getItemViewType_choiceItemType_itemControlExtensionWithDropDown_shouldReturnDropDownViewHolderType() { // ktlint-disable max-line-length + val questionnaireItemAdapter = QuestionnaireItemAdapter() + questionnaireItemAdapter.submitList( + listOf( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.CHOICE) + ) + .addExtension( + Extension.newBuilder() + .setUrl(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_URL)) + .setValue( + Extension.ValueX.newBuilder() + .setCodeableConcept( + CodeableConcept.newBuilder() + .addCoding( + Coding.newBuilder() + .setCode(Code.newBuilder().setValue(ITEM_CONTROL_DROP_DOWN)) + .setDisplay(String.newBuilder().setValue("Drop Down")) + .setSystem(Uri.newBuilder().setValue(EXTENSION_ITEM_CONTROL_SYSTEM)) ) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} + ) + ) ) - ) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) - assertThat(questionnaireItemAdapter.getItemViewType(0)) - .isEqualTo(QuestionnaireItemViewHolderType.DROP_DOWN.value - ) - } + assertThat(questionnaireItemAdapter.getItemViewType(0)) + .isEqualTo(QuestionnaireItemViewHolderType.DROP_DOWN.value) + } - // TODO: test errors thrown for unsupported types + // TODO: test errors thrown for unsupported types - @Test - fun diffCallback_areItemsTheSame_sameLinkId_shouldReturnTrue() { - assertThat( - DiffCallback.areItemsTheSame( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .setText(String.newBuilder().setValue("text")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {}, - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - ).isTrue() - } + @Test + fun diffCallback_areItemsTheSame_sameLinkId_shouldReturnTrue() { + assertThat( + DiffCallback.areItemsTheSame( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .setText(String.newBuilder().setValue("text")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {}, + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) + .isTrue() + } - @Test - fun diffCallback_areItemsTheSame_differentLinkId_shouldReturnFalse() { - assertThat( - DiffCallback.areItemsTheSame( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {}, - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-2")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - ).isFalse() - } + @Test + fun diffCallback_areItemsTheSame_differentLinkId_shouldReturnFalse() { + assertThat( + DiffCallback.areItemsTheSame( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {}, + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-2")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) + .isFalse() + } - @Test - fun diffCallback_areContentsTheSame_sameContents_shouldReturnTrue() { - assertThat( - DiffCallback.areContentsTheSame( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .setText(String.newBuilder().setValue("text")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {}, - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .setText(String.newBuilder().setValue("text")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - ).isTrue() - } + @Test + fun diffCallback_areContentsTheSame_sameContents_shouldReturnTrue() { + assertThat( + DiffCallback.areContentsTheSame( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .setText(String.newBuilder().setValue("text")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {}, + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .setText(String.newBuilder().setValue("text")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) + .isTrue() + } - @Test - fun diffCallback_areContentsTheSame_differentContents_shouldReturnFalse() { - assertThat( - DiffCallback.areContentsTheSame( - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .setText(String.newBuilder().setValue("text")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {}, - QuestionnaireItemViewItem( - Questionnaire.Item.newBuilder() - .setLinkId(String.newBuilder().setValue("link-id-1")) - .setText(String.newBuilder().setValue("different text")) - .build(), - QuestionnaireResponse.Item.newBuilder() - ) {} - ) - ).isFalse() - } + @Test + fun diffCallback_areContentsTheSame_differentContents_shouldReturnFalse() { + assertThat( + DiffCallback.areContentsTheSame( + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .setText(String.newBuilder().setValue("text")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {}, + QuestionnaireItemViewItem( + Questionnaire.Item.newBuilder() + .setLinkId(String.newBuilder().setValue("link-id-1")) + .setText(String.newBuilder().setValue("different text")) + .build(), + QuestionnaireResponse.Item.newBuilder() + ) {} + ) + ) + .isFalse() + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderTypeTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderTypeTest.kt index 32af598c48..6c049edb34 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderTypeTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireItemViewHolderTypeTest.kt @@ -26,15 +26,15 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class QuestionnaireItemViewHolderTypeTest { - @Test - fun size_shouldReturnNumberOfQuestionnaireViewHolderTypes() { - assertThat(QuestionnaireItemViewHolderType.values().size).isEqualTo(12) - } + @Test + fun size_shouldReturnNumberOfQuestionnaireViewHolderTypes() { + assertThat(QuestionnaireItemViewHolderType.values().size).isEqualTo(12) + } - @Test - fun fromInt_shouldReturnQuestionnaireViewHolderType() { - QuestionnaireItemViewHolderType.values().forEach { - assertThat(QuestionnaireItemViewHolderType.fromInt(it.value)).isEqualTo(it) - } + @Test + fun fromInt_shouldReturnQuestionnaireViewHolderType() { + QuestionnaireItemViewHolderType.values().forEach { + assertThat(QuestionnaireItemViewHolderType.fromInt(it.value)).isEqualTo(it) } + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt index 2b18dc65d5..be249451ed 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt @@ -37,135 +37,180 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class QuestionnaireViewModelTest { - private lateinit var state: SavedStateHandle + private lateinit var state: SavedStateHandle - @Before - fun setUp() { - state = SavedStateHandle() - } + @Before + fun setUp() { + state = SavedStateHandle() + } - @Test - fun questionnaireResponse_shouldCopyQuestionnaireId() { - val questionnaire = Questionnaire.newBuilder().apply { - // TODO: if id = a-questionnaire, the json parser sets questionnaire.id.myCoercedValue = - // "Questionnaire/a-questionniare" when decoding which results in the test failing - id = Id.newBuilder().setValue("a-questionnaire").build() - }.build() - val serializedQuestionniare = printer.print(questionnaire) - state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) - val viewModel = QuestionnaireViewModel(state) - assertResourceEquals( - viewModel.getQuestionnaireResponse(), - QuestionnaireResponse.newBuilder().apply { - this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() - }.build() - ) - } + @Test + fun questionnaireResponse_shouldCopyQuestionnaireId() { + val questionnaire = + Questionnaire.newBuilder() + .apply { + // TODO: if id = a-questionnaire, the json parser sets + // questionnaire.id.myCoercedValue + // = + // "Questionnaire/a-questionniare" when decoding which results in the test + // failing + id = Id.newBuilder().setValue("a-questionnaire").build() + } + .build() + val serializedQuestionniare = printer.print(questionnaire) + state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) + val viewModel = QuestionnaireViewModel(state) + assertResourceEquals( + viewModel.getQuestionnaireResponse(), + QuestionnaireResponse.newBuilder() + .apply { this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() } + .build() + ) + } - @Test - fun questionnaireResponse_shouldCopyQuestion() { - val questionnaire = Questionnaire.newBuilder().apply { - id = Id.newBuilder().setValue("a-questionnaire").build() - addItem(Questionnaire.Item.newBuilder().apply { + @Test + fun questionnaireResponse_shouldCopyQuestion() { + val questionnaire = + Questionnaire.newBuilder() + .apply { + id = Id.newBuilder().setValue("a-questionnaire").build() + addItem( + Questionnaire.Item.newBuilder() + .apply { linkId = String.newBuilder().setValue("a-link-id").build() text = String.newBuilder().setValue("Yes or no?").build() - type = Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN).build() - }.build()) - }.build() - val serializedQuestionniare = printer.print(questionnaire) - state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) - val viewModel = QuestionnaireViewModel(state) - assertResourceEquals( - viewModel.getQuestionnaireResponse(), - QuestionnaireResponse.newBuilder().apply { - this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() - addItem(QuestionnaireResponse.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("a-link-id").build() - }.build()) - }.build() - ) - } - - @Test - fun questionnaireResponse_group_shouldCopyQuestionnaireStructure() { - val questionnaire = Questionnaire.newBuilder().apply { - id = Id.newBuilder().setValue("a-questionnaire").build() - addItem(Questionnaire.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("a-link-id").build() - text = String.newBuilder().setValue("Basic questions").build() - type = Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.GROUP).build() - addItem(Questionnaire.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("another-link-id").build() - text = String.newBuilder().setValue("Name?").build() - type = Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.STRING).build() - }) - }) - }.build() - val serializedQuestionniare = printer.print(questionnaire) - state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) - val viewModel = QuestionnaireViewModel(state) - assertResourceEquals( - viewModel.getQuestionnaireResponse(), - QuestionnaireResponse.newBuilder().apply { - this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() - addItem(QuestionnaireResponse.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("a-link-id").build() - addItem(QuestionnaireResponse.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("another-link-id").build() - }) - }) - }.build() - ) - } + type = + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + .build() + } + .build() + ) + } + .build() + val serializedQuestionniare = printer.print(questionnaire) + state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) + val viewModel = QuestionnaireViewModel(state) + assertResourceEquals( + viewModel.getQuestionnaireResponse(), + QuestionnaireResponse.newBuilder() + .apply { + this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() + addItem( + QuestionnaireResponse.Item.newBuilder() + .apply { linkId = String.newBuilder().setValue("a-link-id").build() } + .build() + ) + } + .build() + ) + } - @Test - fun questionnaireItemViewItemList_shouldGenerateQuestionnaireItemViewItemList() = runBlocking { - val questionnaire = Questionnaire.newBuilder().apply { - id = Id.newBuilder().setValue("a-questionnaire").build() - addItem(Questionnaire.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("a-link-id").build() - text = String.newBuilder().setValue("Basic questions").build() - type = Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.GROUP).build() - addItem(Questionnaire.Item.newBuilder().apply { - linkId = String.newBuilder().setValue("another-link-id").build() - text = String.newBuilder().setValue("Name?").build() - type = Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.STRING).build() - }) - }) - }.build() - val serializedQuestionnaire = printer.print(questionnaire) - state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionnaire) - val viewModel = QuestionnaireViewModel(state) - var questionnaireItemViewItemList = viewModel.questionnaireItemViewItemList - questionnaireItemViewItemList[0].questionnaireResponseItemChangedCallback() - assertThat(questionnaireItemViewItemList.size).isEqualTo(2) - val firstQuestionnaireItemViewItem = questionnaireItemViewItemList[0] - val firstQuestionnaireItem = firstQuestionnaireItemViewItem.questionnaireItem - assertThat(firstQuestionnaireItem.linkId.value).isEqualTo("a-link-id") - assertThat(firstQuestionnaireItem.text.value).isEqualTo("Basic questions") - assertThat(firstQuestionnaireItem.type.value) - .isEqualTo(QuestionnaireItemTypeCode.Value.GROUP) - assertThat(firstQuestionnaireItemViewItem.questionnaireResponseItemBuilder.linkId.value) - .isEqualTo("a-link-id") - val secondQuestionnaireItemViewItem = questionnaireItemViewItemList[1] - val secondQuestionnaireItem = secondQuestionnaireItemViewItem.questionnaireItem - assertThat(secondQuestionnaireItem.linkId.value).isEqualTo("another-link-id") - assertThat(secondQuestionnaireItem.text.value).isEqualTo("Name?") - assertThat(secondQuestionnaireItem.type.value) - .isEqualTo(QuestionnaireItemTypeCode.Value.STRING) - assertThat(secondQuestionnaireItemViewItem.questionnaireResponseItemBuilder.linkId.value) - .isEqualTo("another-link-id") - } + @Test + fun questionnaireResponse_group_shouldCopyQuestionnaireStructure() { + val questionnaire = + Questionnaire.newBuilder() + .apply { + id = Id.newBuilder().setValue("a-questionnaire").build() + addItem( + Questionnaire.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("a-link-id").build() + text = String.newBuilder().setValue("Basic questions").build() + type = + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.GROUP) + .build() + addItem( + Questionnaire.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("another-link-id").build() + text = String.newBuilder().setValue("Name?").build() + type = + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.STRING) + .build() + } + ) + } + ) + } + .build() + val serializedQuestionniare = printer.print(questionnaire) + state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionniare) + val viewModel = QuestionnaireViewModel(state) + assertResourceEquals( + viewModel.getQuestionnaireResponse(), + QuestionnaireResponse.newBuilder() + .apply { + this.questionnaire = Canonical.newBuilder().setValue("a-questionnaire").build() + addItem( + QuestionnaireResponse.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("a-link-id").build() + addItem( + QuestionnaireResponse.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("another-link-id").build() + } + ) + } + ) + } + .build() + ) + } - private companion object { - val printer: JsonFormat.Printer = JsonFormat.getPrinter() - fun assertResourceEquals(r1: Message, r2: Message) { - assertThat(printer.print(r1)).isEqualTo(printer.print(r2)) + @Test + fun questionnaireItemViewItemList_shouldGenerateQuestionnaireItemViewItemList() = runBlocking { + val questionnaire = + Questionnaire.newBuilder() + .apply { + id = Id.newBuilder().setValue("a-questionnaire").build() + addItem( + Questionnaire.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("a-link-id").build() + text = String.newBuilder().setValue("Basic questions").build() + type = + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.GROUP) + .build() + addItem( + Questionnaire.Item.newBuilder().apply { + linkId = String.newBuilder().setValue("another-link-id").build() + text = String.newBuilder().setValue("Name?").build() + type = + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.STRING) + .build() + } + ) + } + ) } + .build() + val serializedQuestionnaire = printer.print(questionnaire) + state.set(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE, serializedQuestionnaire) + val viewModel = QuestionnaireViewModel(state) + var questionnaireItemViewItemList = viewModel.questionnaireItemViewItemList + questionnaireItemViewItemList[0].questionnaireResponseItemChangedCallback() + assertThat(questionnaireItemViewItemList.size).isEqualTo(2) + val firstQuestionnaireItemViewItem = questionnaireItemViewItemList[0] + val firstQuestionnaireItem = firstQuestionnaireItemViewItem.questionnaireItem + assertThat(firstQuestionnaireItem.linkId.value).isEqualTo("a-link-id") + assertThat(firstQuestionnaireItem.text.value).isEqualTo("Basic questions") + assertThat(firstQuestionnaireItem.type.value).isEqualTo(QuestionnaireItemTypeCode.Value.GROUP) + assertThat(firstQuestionnaireItemViewItem.questionnaireResponseItemBuilder.linkId.value) + .isEqualTo("a-link-id") + val secondQuestionnaireItemViewItem = questionnaireItemViewItemList[1] + val secondQuestionnaireItem = secondQuestionnaireItemViewItem.questionnaireItem + assertThat(secondQuestionnaireItem.linkId.value).isEqualTo("another-link-id") + assertThat(secondQuestionnaireItem.text.value).isEqualTo("Name?") + assertThat(secondQuestionnaireItem.type.value).isEqualTo(QuestionnaireItemTypeCode.Value.STRING) + assertThat(secondQuestionnaireItemViewItem.questionnaireResponseItemBuilder.linkId.value) + .isEqualTo("another-link-id") + } + + private companion object { + val printer: JsonFormat.Printer = JsonFormat.getPrinter() + fun assertResourceEquals(r1: Message, r2: Message) { + assertThat(printer.print(r1)).isEqualTo(printer.print(r2)) } + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluatorTest.kt index 426b8c0535..11242880a6 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluatorTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/enablement/EnablementEvaluatorTest.kt @@ -33,574 +33,642 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class EnablementEvaluatorTest { - @Test - fun evaluate_noEnableWhen_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder().build()) { null } - ).isTrue() - } + @Test + fun evaluate_noEnableWhen_shouldReturnTrue() { + assertThat(EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder().build()) { null }) + .isTrue() + } - @Test - fun evaluate_missingQuestion_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - ) - .build()) { null } - ).isTrue() - } + @Test + fun evaluate_missingQuestion_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + ) + .build() + ) { null } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerExists_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.getDefaultInstance()) - .build() - } else { - null - } - } - ).isTrue() - } + @Test + fun evaluate_expectsAnswer_answerExists_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer(QuestionnaireResponse.Item.Answer.getDefaultInstance()) + .build() + } else { + null + } + } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerDoesNotExist_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.getDefaultInstance() - } else { - null - } - } - ).isFalse() - } + @Test + fun evaluate_expectsAnswer_answerDoesNotExist_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.getDefaultInstance() + } else { + null + } + } + ) + .isFalse() + } - @Test - fun evaluate_expectsNoAnswer_answerExists_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.getDefaultInstance()) - .build() - } else { - null - } - } - ).isFalse() - } + @Test + fun evaluate_expectsNoAnswer_answerExists_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer(QuestionnaireResponse.Item.Answer.getDefaultInstance()) + .build() + } else { + null + } + } + ) + .isFalse() + } - @Test - fun evaluate_expectsNoAnswer_answerDoesNotExist_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.getDefaultInstance() - } else { - null - } - } - ).isTrue() - } + @Test + fun evaluate_expectsNoAnswer_answerDoesNotExist_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.getDefaultInstance() + } else { + null + } + } + ) + .isTrue() + } - @Test - fun evaluate_anyEnableWhens_noneSatisfied_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q2")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setEnableBehavior( - Questionnaire.Item.EnableBehaviorCode.newBuilder() - .setValue(EnableWhenBehaviorCode.Value.ANY) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - when (it) { - "q1" -> QuestionnaireResponse.Item.getDefaultInstance() - "q2" -> QuestionnaireResponse.Item.getDefaultInstance() - else -> null - } - } - ).isFalse() - } + @Test + fun evaluate_anyEnableWhens_noneSatisfied_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q2")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setEnableBehavior( + Questionnaire.Item.EnableBehaviorCode.newBuilder() + .setValue(EnableWhenBehaviorCode.Value.ANY) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + when (it) { + "q1" -> QuestionnaireResponse.Item.getDefaultInstance() + "q2" -> QuestionnaireResponse.Item.getDefaultInstance() + else -> null + } + } + ) + .isFalse() + } - @Test - fun evaluate_anyEnableWhens_oneSatisfied_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q2")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setEnableBehavior( - Questionnaire.Item.EnableBehaviorCode.newBuilder() - .setValue(EnableWhenBehaviorCode.Value.ANY) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - when (it) { - "q1" -> QuestionnaireResponse.Item.getDefaultInstance() - "q2" -> QuestionnaireResponse.Item.getDefaultInstance() - else -> null - } - } - ).isTrue() - } + @Test + fun evaluate_anyEnableWhens_oneSatisfied_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q2")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setEnableBehavior( + Questionnaire.Item.EnableBehaviorCode.newBuilder() + .setValue(EnableWhenBehaviorCode.Value.ANY) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + when (it) { + "q1" -> QuestionnaireResponse.Item.getDefaultInstance() + "q2" -> QuestionnaireResponse.Item.getDefaultInstance() + else -> null + } + } + ) + .isTrue() + } - @Test - fun evaluate_allEnableWhens_someSatisfied_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q2")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setEnableBehavior(Questionnaire.Item.EnableBehaviorCode.newBuilder() - .setValue(EnableWhenBehaviorCode.Value.ALL)) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - when (it) { - "q1" -> QuestionnaireResponse.Item.getDefaultInstance() - "q2" -> QuestionnaireResponse.Item.getDefaultInstance() - else -> null - } - } - ).isFalse() - } + @Test + fun evaluate_allEnableWhens_someSatisfied_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q2")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setEnableBehavior( + Questionnaire.Item.EnableBehaviorCode.newBuilder() + .setValue(EnableWhenBehaviorCode.Value.ALL) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + when (it) { + "q1" -> QuestionnaireResponse.Item.getDefaultInstance() + "q2" -> QuestionnaireResponse.Item.getDefaultInstance() + else -> null + } + } + ) + .isFalse() + } - @Test - fun evaluate_allEnableWhens_allSatisfied_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q2")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(false).build() - } - ) - ) - .setEnableBehavior(Questionnaire.Item.EnableBehaviorCode.newBuilder() - .setValue(EnableWhenBehaviorCode.Value.ALL)) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - when (it) { - "q1" -> QuestionnaireResponse.Item.getDefaultInstance() - "q2" -> QuestionnaireResponse.Item.getDefaultInstance() - else -> null - } - } - ).isTrue() - } + @Test + fun evaluate_allEnableWhens_allSatisfied_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q2")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EXISTS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(false).build() + } + ) + ) + .setEnableBehavior( + Questionnaire.Item.EnableBehaviorCode.newBuilder() + .setValue(EnableWhenBehaviorCode.Value.ALL) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + when (it) { + "q1" -> QuestionnaireResponse.Item.getDefaultInstance() + "q2" -> QuestionnaireResponse.Item.getDefaultInstance() + else -> null + } + } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerEqual_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerEqual_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true).build()) + .build() } - } - ).isTrue() - } + ) + .build() + } else { + null + } + } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerDoesNotEqual_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(false).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerDoesNotEqual_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(false).build()) + .build() } - } - ).isFalse() - } + ) + .build() + } else { + null + } + } + ) + .isFalse() + } - @Test - fun evaluate_expectsAnswer_answerEqualOne_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true).build()) - .build() - } - ) - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(false).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerEqualOne_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.EQUALS) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true).build()) + .build() } - } - ).isTrue() - } + ) + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(false).build()) + .build() + } + ) + .build() + } else { + null + } + } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerNotEqual_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(false).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerNotEqual_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(false).build()) + .build() } - } - ).isTrue() - } + ) + .build() + } else { + null + } + } + ) + .isTrue() + } - @Test - fun evaluate_expectsAnswer_answerDoesNotNotEqual_shouldReturnFalse() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType(Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN)) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerDoesNotNotEqual_shouldReturnFalse() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true).build()) + .build() } - } - ).isFalse() - } + ) + .build() + } else { + null + } + } + ) + .isFalse() + } - @Test - fun evaluate_expectsAnswer_answerNotEqualOne_shouldReturnTrue() { - assertThat( - EnablementEvaluator.evaluate(Questionnaire.Item.newBuilder() - .addEnableWhen( - Questionnaire.Item.EnableWhen.newBuilder() - .setQuestion(String.newBuilder().setValue("q1")) - .setOperator( - Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() - .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) - ) - .setAnswer( - Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { - boolean = Boolean.newBuilder().setValue(true).build() - } - ) - ) - .setType( - Questionnaire.Item.TypeCode.newBuilder() - .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) - ) - .build() - ) { - if (it == "q1") { - QuestionnaireResponse.Item.newBuilder() - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true).build()) - .build() - } - ) - .addAnswer(QuestionnaireResponse.Item.Answer.newBuilder() - .apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(false).build()) - .build() - } - ) - .build() - } else { - null + @Test + fun evaluate_expectsAnswer_answerNotEqualOne_shouldReturnTrue() { + assertThat( + EnablementEvaluator.evaluate( + Questionnaire.Item.newBuilder() + .addEnableWhen( + Questionnaire.Item.EnableWhen.newBuilder() + .setQuestion(String.newBuilder().setValue("q1")) + .setOperator( + Questionnaire.Item.EnableWhen.OperatorCode.newBuilder() + .setValue(QuestionnaireItemOperatorCode.Value.NOT_EQUAL_TO) + ) + .setAnswer( + Questionnaire.Item.EnableWhen.AnswerX.newBuilder().apply { + boolean = Boolean.newBuilder().setValue(true).build() + } + ) + ) + .setType( + Questionnaire.Item.TypeCode.newBuilder() + .setValue(QuestionnaireItemTypeCode.Value.BOOLEAN) + ) + .build() + ) { + if (it == "q1") { + QuestionnaireResponse.Item.newBuilder() + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true).build()) + .build() + } + ) + .addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(false).build()) + .build() } - } - ).isTrue() - } + ) + .build() + } else { + null + } + } + ) + .isTrue() + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt index 1b20b23b94..7829723473 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/mapping/ResourceMapperTest.kt @@ -33,10 +33,11 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class ResourceMapperTest { - @Test - fun extract() { - // https://developer.commure.com/docs/apis/sdc/examples#definition-based-extraction - val questionnaireJson = """ + @Test + fun extract() { + // https://developer.commure.com/docs/apis/sdc/examples#definition-based-extraction + val questionnaireJson = + """ { "resourceType": "Questionnaire", "subjectType": [ @@ -72,10 +73,11 @@ class ResourceMapperTest { ] } """.trimIndent() - val questionnaireBuilder = Questionnaire.newBuilder() - JsonFormat.getParser().merge(questionnaireJson, questionnaireBuilder) + val questionnaireBuilder = Questionnaire.newBuilder() + JsonFormat.getParser().merge(questionnaireJson, questionnaireBuilder) - val questionnaireResponseJson = """ + val questionnaireResponseJson = + """ { "resourceType": "QuestionnaireResponse", "item": [ @@ -103,24 +105,23 @@ class ResourceMapperTest { ] } """.trimIndent() - val questionnaireResponseBuilder = QuestionnaireResponse.newBuilder() - JsonFormat.getParser().merge(questionnaireResponseJson, questionnaireResponseBuilder) + val questionnaireResponseBuilder = QuestionnaireResponse.newBuilder() + JsonFormat.getParser().merge(questionnaireResponseJson, questionnaireResponseBuilder) - val patient = ResourceMapper.extract( - questionnaireBuilder.build(), - questionnaireResponseBuilder.build() - ) as Patient + val patient = + ResourceMapper.extract(questionnaireBuilder.build(), questionnaireResponseBuilder.build()) as + Patient - assertThat(patient.birthDate).isEqualTo( - Date.newBuilder() - .setValueUs( - LocalDate.of(2021, 1, 1) - .atStartOfDay(ZoneId.systemDefault()) - .toEpochSecond() * 1000000) - .setTimezone(ZoneId.systemDefault().id) - .setPrecision(Date.Precision.DAY) - .build() - ) - assertThat(patient.active.value).isTrue() - } + assertThat(patient.birthDate) + .isEqualTo( + Date.newBuilder() + .setValueUs( + LocalDate.of(2021, 1, 1).atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000000 + ) + .setTimezone(ZoneId.systemDefault().id) + .setPrecision(Date.Precision.DAY) + .build() + ) + assertThat(patient.active.value).isTrue() + } } diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItemTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItemTest.kt index 70ca0757b0..46322f33dc 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItemTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/views/QuestionnaireItemViewItemTest.kt @@ -29,53 +29,59 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.P]) class QuestionnaireItemViewItemTest { - @Test - fun singleAnswerOrNull_noAnswer_shouldReturnNull() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder() - ) {} - assertThat(questionnaireItemViewItem.singleAnswerOrNull).isNull() - } + @Test + fun singleAnswerOrNull_noAnswer_shouldReturnNull() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder() + ) {} + assertThat(questionnaireItemViewItem.singleAnswerOrNull).isNull() + } - @Test - fun singleAnswerOrNull_singleAnswer_shouldReturnSingleAnswer() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().apply { - addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true)) - .build() - } - ) + @Test + fun singleAnswerOrNull_singleAnswer_shouldReturnSingleAnswer() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder().apply { + addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true)) + .build() } - ) {} - assertThat(questionnaireItemViewItem.singleAnswerOrNull!!.value.boolean.value).isTrue() - } + ) + } + ) {} + assertThat(questionnaireItemViewItem.singleAnswerOrNull!!.value.boolean.value).isTrue() + } - @Test - fun singleAnswerOrNull_multipleAnswers_shouldReturnNull() { - val questionnaireItemViewItem = QuestionnaireItemViewItem( - Questionnaire.Item.getDefaultInstance(), - QuestionnaireResponse.Item.newBuilder().apply { - addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true)) - .build() - } - ) - addAnswer( - QuestionnaireResponse.Item.Answer.newBuilder().apply { - value = QuestionnaireResponse.Item.Answer.ValueX.newBuilder() - .setBoolean(Boolean.newBuilder().setValue(true)) - .build() - } - ) + @Test + fun singleAnswerOrNull_multipleAnswers_shouldReturnNull() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.Item.getDefaultInstance(), + QuestionnaireResponse.Item.newBuilder().apply { + addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true)) + .build() } - ) {} - assertThat(questionnaireItemViewItem.singleAnswerOrNull).isNull() - } + ) + addAnswer( + QuestionnaireResponse.Item.Answer.newBuilder().apply { + value = + QuestionnaireResponse.Item.Answer.ValueX.newBuilder() + .setBoolean(Boolean.newBuilder().setValue(true)) + .build() + } + ) + } + ) {} + assertThat(questionnaireItemViewItem.singleAnswerOrNull).isNull() + } } diff --git a/datacapturegallery/src/androidTest/java/com/google/android/fhir/datacapture/gallery/ExampleInstrumentedTest.kt b/datacapturegallery/src/androidTest/java/com/google/android/fhir/datacapture/gallery/ExampleInstrumentedTest.kt index 07afe4f1b3..5cea8562ea 100644 --- a/datacapturegallery/src/androidTest/java/com/google/android/fhir/datacapture/gallery/ExampleInstrumentedTest.kt +++ b/datacapturegallery/src/androidTest/java/com/google/android/fhir/datacapture/gallery/ExampleInstrumentedTest.kt @@ -29,10 +29,10 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - Assert.assertEquals("com.google.android.fhir.datacapture.gallery", appContext.packageName) - } + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + Assert.assertEquals("com.google.android.fhir.datacapture.gallery", appContext.packageName) + } } diff --git a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/MainActivity.kt b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/MainActivity.kt index fa577a6daa..6b9bccb12c 100644 --- a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/MainActivity.kt +++ b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/MainActivity.kt @@ -21,56 +21,58 @@ import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.RecyclerView class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) - val recyclerView = findViewById(R.id.recycler_view) - recyclerView.adapter = QuestionnaireListAdapter( - listOf( - // Example taken from https://www.hl7.org/fhir/questionnaire-example-f201-lifelines.json.html - QuestionnaireListItem( - "Real-world lifelines questionnaire", - "HL7 example \"f201\"", - "hl7-questionnaire-example-f201-lifelines.json" - ), - // Example taken from https://www.hl7.org/fhir/questionnaire-example-bluebook.json.html - QuestionnaireListItem( - "Neonate record from New South Wales, Australia", - "HL7 example \"bb\"", - "hl7-questionnaire-example-bluebook.json" - ), - // Example authored by fredhersch@google.com. - QuestionnaireListItem( - "Patient registration", - "Example authored by Fred Hersch", - "patient-registration.json" - ), - // Example taken from https://openhie.github.io/hiv-ig/Questionnaire-hiv-case-report-questionnaire.json.html - QuestionnaireListItem( - "HIV Case Report", - "HIV Case Reporting and Monitoring IG", - "openhie-hiv-case-report.json" - ), - // Example taken from https://openhie.github.io/covid-ig/Questionnaire-WhoCrQuestionnaireCovid19Surveillance.json.html - QuestionnaireListItem( - "COVID-19 Case Report", - "WHO Case Reporting for COVID-19 Surveillance IG", - "openhie-covid-case-report.json" - ), - // Example taken from https://github.com/cqframework/cds4cpm-mypain/blob/master/src/content/mypain-questionnaire.json - QuestionnaireListItem( - "MyPAIN", - "CDS For Chronic Pain Management", - "cds4cpm-mypain.json" - ), - // IPRD-WITS Clinic HIV Risk assessment questionnaire authored by @joiskash - QuestionnaireListItem( - "HIV-Risk Assessment", - "HIV-Risk Assessment", - "iprd-hiv-fhir-questionnaire.json" - ) - ) + val recyclerView = findViewById(R.id.recycler_view) + recyclerView.adapter = + QuestionnaireListAdapter( + listOf( + // Example taken from + // https://www.hl7.org/fhir/questionnaire-example-f201-lifelines.json.html + QuestionnaireListItem( + "Real-world lifelines questionnaire", + "HL7 example \"f201\"", + "hl7-questionnaire-example-f201-lifelines.json" + ), + // Example taken from + // https://www.hl7.org/fhir/questionnaire-example-bluebook.json.html + QuestionnaireListItem( + "Neonate record from New South Wales, Australia", + "HL7 example \"bb\"", + "hl7-questionnaire-example-bluebook.json" + ), + // Example authored by fredhersch@google.com. + QuestionnaireListItem( + "Patient registration", + "Example authored by Fred Hersch", + "patient-registration.json" + ), + // Example taken from + // https://openhie.github.io/hiv-ig/Questionnaire-hiv-case-report-questionnaire.json.html + QuestionnaireListItem( + "HIV Case Report", + "HIV Case Reporting and Monitoring IG", + "openhie-hiv-case-report.json" + ), + // Example taken from + // https://openhie.github.io/covid-ig/Questionnaire-WhoCrQuestionnaireCovid19Surveillance.json.html + QuestionnaireListItem( + "COVID-19 Case Report", + "WHO Case Reporting for COVID-19 Surveillance IG", + "openhie-covid-case-report.json" + ), + // Example taken from + // https://github.com/cqframework/cds4cpm-mypain/blob/master/src/content/mypain-questionnaire.json + QuestionnaireListItem("MyPAIN", "CDS For Chronic Pain Management", "cds4cpm-mypain.json"), + // IPRD-WITS Clinic HIV Risk assessment questionnaire authored by @joiskash + QuestionnaireListItem( + "HIV-Risk Assessment", + "HIV-Risk Assessment", + "iprd-hiv-fhir-questionnaire.json" + ) ) - } + ) + } } diff --git a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireActivity.kt b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireActivity.kt index e29d79e5b0..688489878d 100644 --- a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireActivity.kt +++ b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireActivity.kt @@ -28,64 +28,57 @@ import com.google.fhir.common.JsonFormat import com.google.fhir.r4.core.QuestionnaireResponse class QuestionnaireActivity : AppCompatActivity() { - private val viewModel: QuestionnaireViewModel by viewModels() + private val viewModel: QuestionnaireViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_questionnaire) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_questionnaire) - supportActionBar!!.apply { - title = intent.getStringExtra(QUESTIONNAIRE_TITLE_KEY) - setDisplayHomeAsUpEnabled(true) - } + supportActionBar!!.apply { + title = intent.getStringExtra(QUESTIONNAIRE_TITLE_KEY) + setDisplayHomeAsUpEnabled(true) + } - // Only add the fragment once, when the activity is first created. - if (savedInstanceState == null) { - val fragment = QuestionnaireFragment() - fragment.arguments = bundleOf( - QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE to viewModel.questionnaire - ) + // Only add the fragment once, when the activity is first created. + if (savedInstanceState == null) { + val fragment = QuestionnaireFragment() + fragment.arguments = + bundleOf(QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE to viewModel.questionnaire) - supportFragmentManager.commit { - add(R.id.container, fragment, QUESTIONNAIRE_FRAGMENT_TAG) - } - } + supportFragmentManager.commit { add(R.id.container, fragment, QUESTIONNAIRE_FRAGMENT_TAG) } } + } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.top_bar_menu, menu) - return true - } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.top_bar_menu, menu) + return true + } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.getItemId()) { - R.id.action_submit -> { - val questionnaireFragment = supportFragmentManager.findFragmentByTag( - QUESTIONNAIRE_FRAGMENT_TAG - ) as QuestionnaireFragment - displayQuestionnaireResponse(questionnaireFragment.getQuestionnaireResponse()) - true - } - else -> super.onOptionsItemSelected(item) - } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.getItemId()) { + R.id.action_submit -> { + val questionnaireFragment = + supportFragmentManager.findFragmentByTag(QUESTIONNAIRE_FRAGMENT_TAG) as + QuestionnaireFragment + displayQuestionnaireResponse(questionnaireFragment.getQuestionnaireResponse()) + true + } + else -> super.onOptionsItemSelected(item) } + } - // Display Quesitonnaire response as a dialog - fun displayQuestionnaireResponse(questionnaireResponse: QuestionnaireResponse) { - val questionnaireResponseJson = JsonFormat.getPrinter().print(questionnaireResponse) - val dialogFragment = QuestionnaireResponseDialogFragment() - dialogFragment.arguments = bundleOf( - QuestionnaireResponseDialogFragment.BUNDLE_KEY_CONTENTS to questionnaireResponseJson - ) - dialogFragment.show( - supportFragmentManager, - QuestionnaireResponseDialogFragment.TAG - ) - } + // Display Quesitonnaire response as a dialog + fun displayQuestionnaireResponse(questionnaireResponse: QuestionnaireResponse) { + val questionnaireResponseJson = JsonFormat.getPrinter().print(questionnaireResponse) + val dialogFragment = QuestionnaireResponseDialogFragment() + dialogFragment.arguments = + bundleOf(QuestionnaireResponseDialogFragment.BUNDLE_KEY_CONTENTS to questionnaireResponseJson) + dialogFragment.show(supportFragmentManager, QuestionnaireResponseDialogFragment.TAG) + } - companion object { - const val QUESTIONNAIRE_TITLE_KEY = "questionnaire-title-key" - const val QUESTIONNAIRE_FILE_PATH_KEY = "questionnaire-file-path-key" - const val QUESTIONNAIRE_FRAGMENT_TAG = "questionannire-fragment-tag" - } + companion object { + const val QUESTIONNAIRE_TITLE_KEY = "questionnaire-title-key" + const val QUESTIONNAIRE_FILE_PATH_KEY = "questionnaire-file-path-key" + const val QUESTIONNAIRE_FRAGMENT_TAG = "questionannire-fragment-tag" + } } diff --git a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireListAdapter.kt b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireListAdapter.kt index f52f144c9e..cca036f3fb 100644 --- a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireListAdapter.kt +++ b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireListAdapter.kt @@ -24,43 +24,39 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.Adapter -class QuestionnaireListAdapter( - private val questionnaireList: List -) : Adapter() { - - class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val name: TextView = view.findViewById(R.id.questionnaire_name) - val description: TextView = view.findViewById(R.id.questionnaire_description) - lateinit var questionnairelistItem: QuestionnaireListItem - - init { - view.setOnClickListener { - val context = view.context - context.startActivity(Intent(context, QuestionnaireActivity::class.java).apply { - putExtra( - QuestionnaireActivity.QUESTIONNAIRE_TITLE_KEY, - questionnairelistItem.name - ) - putExtra( - QuestionnaireActivity.QUESTIONNAIRE_FILE_PATH_KEY, - questionnairelistItem.path - ) - }) - } - } +class QuestionnaireListAdapter(private val questionnaireList: List) : + Adapter() { + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val name: TextView = view.findViewById(R.id.questionnaire_name) + val description: TextView = view.findViewById(R.id.questionnaire_description) + lateinit var questionnairelistItem: QuestionnaireListItem + + init { + view.setOnClickListener { + val context = view.context + context.startActivity( + Intent(context, QuestionnaireActivity::class.java).apply { + putExtra(QuestionnaireActivity.QUESTIONNAIRE_TITLE_KEY, questionnairelistItem.name) + putExtra(QuestionnaireActivity.QUESTIONNAIRE_FILE_PATH_KEY, questionnairelistItem.path) + } + ) + } } + } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.questionnaire_list_item_view, parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + ViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.questionnaire_list_item_view, parent, false) ) - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val questionnaireListItem = questionnaireList[position] - holder.questionnairelistItem = questionnaireListItem - holder.name.text = questionnaireListItem.name - holder.description.text = questionnaireListItem.description - } + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val questionnaireListItem = questionnaireList[position] + holder.questionnairelistItem = questionnaireListItem + holder.name.text = questionnaireListItem.name + holder.description.text = questionnaireListItem.description + } - override fun getItemCount() = questionnaireList.size + override fun getItemCount() = questionnaireList.size } diff --git a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireResponseDialogFragment.kt b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireResponseDialogFragment.kt index 34485de581..0382275e87 100644 --- a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireResponseDialogFragment.kt +++ b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireResponseDialogFragment.kt @@ -24,23 +24,22 @@ import androidx.fragment.app.DialogFragment class QuestionnaireResponseDialogFragment() : DialogFragment() { - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val contents = requireArguments().getString(BUNDLE_KEY_CONTENTS) - return activity?.let { - val view = requireActivity().layoutInflater.inflate( - R.layout.questionnaire_response_dialog_contents, - null - ) - view.findViewById(R.id.contents).text = contents + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val contents = requireArguments().getString(BUNDLE_KEY_CONTENTS) + return activity?.let { + val view = + requireActivity() + .layoutInflater + .inflate(R.layout.questionnaire_response_dialog_contents, null) + view.findViewById(R.id.contents).text = contents - AlertDialog.Builder(it) - .setView(view) - .create() - } ?: throw IllegalStateException("Activity cannot be null") + AlertDialog.Builder(it).setView(view).create() } + ?: throw IllegalStateException("Activity cannot be null") + } - companion object { - const val TAG = "questionnaire-response-dialog-fragment" - const val BUNDLE_KEY_CONTENTS = "contents" - } + companion object { + const val TAG = "questionnaire-response-dialog-fragment" + const val BUNDLE_KEY_CONTENTS = "contents" + } } diff --git a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireViewModel.kt b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireViewModel.kt index 3eb9a85d44..c0242f59f4 100644 --- a/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireViewModel.kt +++ b/datacapturegallery/src/main/java/com/google/android/fhir/datacapture/gallery/QuestionnaireViewModel.kt @@ -21,16 +21,18 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle class QuestionnaireViewModel(application: Application, private val state: SavedStateHandle) : - AndroidViewModel(application) { - var questionnaireJson: String? = null - val questionnaire: String - get() { - if (questionnaireJson == null) { - questionnaireJson = getApplication().assets - .open(state[QuestionnaireActivity.QUESTIONNAIRE_FILE_PATH_KEY]!!) - .bufferedReader() - .use { it.readText() } - } - return questionnaireJson!! - } + AndroidViewModel(application) { + var questionnaireJson: String? = null + val questionnaire: String + get() { + if (questionnaireJson == null) { + questionnaireJson = + getApplication() + .assets + .open(state[QuestionnaireActivity.QUESTIONNAIRE_FILE_PATH_KEY]!!) + .bufferedReader() + .use { it.readText() } + } + return questionnaireJson!! + } } diff --git a/reference/src/androidTest/java/com/google/android/fhir/reference/ExampleInstrumentedTest.kt b/reference/src/androidTest/java/com/google/android/fhir/reference/ExampleInstrumentedTest.kt index 7342bf5f12..fcf67921e4 100644 --- a/reference/src/androidTest/java/com/google/android/fhir/reference/ExampleInstrumentedTest.kt +++ b/reference/src/androidTest/java/com/google/android/fhir/reference/ExampleInstrumentedTest.kt @@ -29,10 +29,10 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - Assert.assertEquals("com.google.android.fhir.reference", appContext.packageName) - } + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + Assert.assertEquals("com.google.android.fhir.reference", appContext.packageName) + } } diff --git a/reference/src/main/java/com/google/android/fhir/reference/CqlActivityViewModel.kt b/reference/src/main/java/com/google/android/fhir/reference/CqlActivityViewModel.kt index f2a1ec15dd..ed1ba0788b 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/CqlActivityViewModel.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/CqlActivityViewModel.kt @@ -26,40 +26,38 @@ import com.google.android.fhir.sync.SyncData import kotlinx.coroutines.launch import org.hl7.fhir.r4.model.ResourceType -class CqlActivityViewModel( - private val fhirEngine: FhirEngine -) : ViewModel() { +class CqlActivityViewModel(private val fhirEngine: FhirEngine) : ViewModel() { - init { - requestPatients() - } + init { + requestPatients() + } - private fun requestPatients() { - viewModelScope.launch { - val syncData = listOf( - SyncData( - // For the purpose of demo, sync patients that live in Nairobi. - resourceType = ResourceType.Patient, - // add "_revinclude" to "Observation:subject" to return Observations for - // the patients. - params = mapOf("address-city" to "NAIROBI") - ) - ) + private fun requestPatients() { + viewModelScope.launch { + val syncData = + listOf( + SyncData( + // For the purpose of demo, sync patients that live in Nairobi. + resourceType = ResourceType.Patient, + // add "_revinclude" to "Observation:subject" to return Observations for + // the patients. + params = mapOf("address-city" to "NAIROBI") + ) + ) - val syncConfig = SyncConfiguration(syncData = syncData) - val result = fhirEngine.sync(syncConfig) - Log.d("CqlActivityViewModel", "sync result: $result") - } + val syncConfig = SyncConfiguration(syncData = syncData) + val result = fhirEngine.sync(syncConfig) + Log.d("CqlActivityViewModel", "sync result: $result") } + } } -class CqlLoadActivityViewModelFactory( - private val fhirEngine: FhirEngine -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(CqlActivityViewModel::class.java)) { - return CqlActivityViewModel(fhirEngine) as T - } - throw IllegalArgumentException("Unknown ViewModel class") +class CqlLoadActivityViewModelFactory(private val fhirEngine: FhirEngine) : + ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(CqlActivityViewModel::class.java)) { + return CqlActivityViewModel(fhirEngine) as T } + throw IllegalArgumentException("Unknown ViewModel class") + } } diff --git a/reference/src/main/java/com/google/android/fhir/reference/CqlLoadActivity.kt b/reference/src/main/java/com/google/android/fhir/reference/CqlLoadActivity.kt index 606f9b4b8b..8004269d70 100644 --- a/reference/src/main/java/com/google/android/fhir/reference/CqlLoadActivity.kt +++ b/reference/src/main/java/com/google/android/fhir/reference/CqlLoadActivity.kt @@ -37,116 +37,109 @@ import org.hl7.fhir.r4.model.Resource import org.opencds.cqf.cql.execution.EvaluationResult class CqlLoadActivity : AppCompatActivity() { - lateinit var fhirEngine: FhirEngine - lateinit var cqlLibraryUrlInput: EditText - lateinit var fhirResourceUrlInput: EditText - lateinit var libraryInput: EditText - lateinit var contextInput: EditText - lateinit var expressionInput: EditText - lateinit var evaluationResultTextView: TextView + lateinit var fhirEngine: FhirEngine + lateinit var cqlLibraryUrlInput: EditText + lateinit var fhirResourceUrlInput: EditText + lateinit var libraryInput: EditText + lateinit var contextInput: EditText + lateinit var expressionInput: EditText + lateinit var evaluationResultTextView: TextView - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_cql_load) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_cql_load) - fhirEngine = fhirEngine(this) - cqlLibraryUrlInput = findViewById(R.id.cql_text_input) - fhirResourceUrlInput = findViewById(R.id.fhir_resource_url_input) - libraryInput = findViewById(R.id.library_input) - contextInput = findViewById(R.id.context_input) - expressionInput = findViewById(R.id.expression_input) - evaluationResultTextView = findViewById(R.id.evaluate_result) + fhirEngine = fhirEngine(this) + cqlLibraryUrlInput = findViewById(R.id.cql_text_input) + fhirResourceUrlInput = findViewById(R.id.fhir_resource_url_input) + libraryInput = findViewById(R.id.library_input) + contextInput = findViewById(R.id.context_input) + expressionInput = findViewById(R.id.expression_input) + evaluationResultTextView = findViewById(R.id.evaluate_result) - val viewModel = - ViewModelProvider(this, CqlLoadActivityViewModelFactory(fhirEngine!!)) - .get(CqlActivityViewModel::class.java) + val viewModel = + ViewModelProvider(this, CqlLoadActivityViewModelFactory(fhirEngine!!)) + .get(CqlActivityViewModel::class.java) - val loadCqlLibButton = findViewById