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 877e5ebbb2..5cb9b8fc54 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 @@ -66,6 +66,9 @@ class QuestionnaireFragment : Fragment() { // Returns the current questionnaire response fun getQuestionnaireResponse() = viewModel.getQuestionnaireResponse() + // The current questionnaire + internal val questionnaire = viewModel.questionnaire + companion object { const val BUNDLE_KEY_QUESTIONNAIRE = "questionnaire" const val BUNDLE_KEY_QUESTIONNAIRE_RESPONSE = "questionnaire-response" 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 028c5fddce..6170be6409 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 @@ -30,7 +30,7 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse internal class QuestionnaireViewModel(state: SavedStateHandle) : ViewModel() { /** The current questionnaire as questions are being answered. */ - private val questionnaire: Questionnaire + internal val questionnaire: Questionnaire init { val questionnaireJson: String = state[QuestionnaireFragment.BUNDLE_KEY_QUESTIONNAIRE]!! diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidator.kt index fac6ec37de..da93ee34b3 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidator.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidator.kt @@ -48,4 +48,4 @@ internal object QuestionnaireResponseItemValidator { } } -internal data class ValidationResult(var isValid: Boolean, val validationMessages: List) +data class ValidationResult(var isValid: Boolean, val validationMessages: List) diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidator.kt new file mode 100644 index 0000000000..3e0d405559 --- /dev/null +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidator.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.validation + +import com.google.android.fhir.datacapture.hasNestedItemsWithinAnswers +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse + +object QuestionnaireResponseValidator { + + /** Maps linkId to [ValidationResult]. */ + private val linkIdToValidationResultMap = mutableMapOf>() + + /** + * Validates [questionnaireResponseItemList] using the constraints defined in the + * [questionnaireItemList]. + */ + fun validate( + questionnaireItemList: List, + questionnaireResponseItemList: List + ): Map> { + /* TODO create an iterator for questionnaire item + questionnaire response item refer to the + questionnaire view model */ + val questionnaireItemListIterator = questionnaireItemList.iterator() + val questionnaireResponseItemListIterator = questionnaireResponseItemList.iterator() + while (questionnaireItemListIterator.hasNext() && + questionnaireResponseItemListIterator.hasNext()) { + val questionnaireItem = questionnaireItemListIterator.next() + val questionnaireResponseItem = questionnaireResponseItemListIterator.next() + linkIdToValidationResultMap[questionnaireItem.linkId] = mutableListOf() + linkIdToValidationResultMap[questionnaireItem.linkId]?.add( + QuestionnaireResponseItemValidator.validate(questionnaireItem, questionnaireResponseItem) + ) + if (questionnaireItem.hasNestedItemsWithinAnswers) { + // TODO(https://github.com/google/android-fhir/issues/487): Validates all answers. + validate(questionnaireItem.item, questionnaireResponseItem.answer[0].item) + } + validate(questionnaireItem.item, questionnaireResponseItem.item) + } + return linkIdToValidationResultMap + } +} diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueConstraintValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueConstraintValidatorTest.kt index 4897abc63f..36e44b7b18 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueConstraintValidatorTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueConstraintValidatorTest.kt @@ -55,7 +55,7 @@ class MaxValueConstraintValidatorTest { MaxValueConstraintValidator.validate(questionnaireItem, questionnaireResponseItem) assertThat(validationResult.isValid).isFalse() - assertThat(validationResult.message.equals("Maximum value allowed is:200000")).isTrue() + assertThat(validationResult.message).isEqualTo("Maximum value allowed is:200000") } @Test diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidatorTest.kt index 460ad8d485..5830c25309 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidatorTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemValidatorTest.kt @@ -69,6 +69,7 @@ class QuestionnaireResponseItemValidatorTest { fun exceededMaxMinValue_shouldReturnInvalidResultWithMessages() { val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { + linkId = "a-question" addExtension( Extension().apply { url = MIN_VALUE_EXTENSION_URL @@ -84,6 +85,7 @@ class QuestionnaireResponseItemValidatorTest { } val questionnaireResponseItem = QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { + linkId = "a-question" addAnswer( QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { value = IntegerType(550) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidatorTest.kt new file mode 100644 index 0000000000..293cc405a9 --- /dev/null +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseValidatorTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.fhir.datacapture.validation + +import android.os.Build +import com.google.common.truth.Truth.assertThat +import org.hl7.fhir.r4.model.IntegerType +import org.hl7.fhir.r4.model.Questionnaire +import org.hl7.fhir.r4.model.QuestionnaireResponse +import org.hl7.fhir.r4.model.StringType +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.P]) +class QuestionnaireResponseValidatorTest { + + @Test + fun shouldReturnValidResult() { + val questionnaire = + Questionnaire() + .addItem( + Questionnaire.QuestionnaireItemComponent() + .setLinkId("a-question") + .setMaxLength(3) + .setType(Questionnaire.QuestionnaireItemType.INTEGER) + .setText("Age in years?") + ) + val questionnaireResponse = + QuestionnaireResponse() + .addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .setLinkId("a-question") + .setAnswer( + listOf( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(IntegerType(3)) + ) + ) + ) + val result = + QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item) + assertThat(result.get("a-question")).isEqualTo(listOf(ValidationResult(true, listOf()))) + } + + @Test + fun shouldReturnInvalidResultWithMessages() { + val questionnaire = + Questionnaire() + .addItem( + Questionnaire.QuestionnaireItemComponent() + .setLinkId("a-question") + .setMaxLength(3) + .setType(Questionnaire.QuestionnaireItemType.INTEGER) + .setText("Age in years?") + ) + val questionnaireResponse = + QuestionnaireResponse() + .addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .setLinkId("a-question") + .setAnswer( + listOf( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(IntegerType(1000)) + ) + ) + ) + val result = + QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item) + assertThat(result.get("a-question")) + .isEqualTo( + listOf( + ValidationResult( + false, + listOf("The maximum number of characters that are permitted in the answer is: 3") + ) + ) + ) + } + + @Test + fun shouldReturnInvalidResultWithMessages_forNestedItems() { + val questionnaire = + Questionnaire() + .addItem( + Questionnaire.QuestionnaireItemComponent() + .setLinkId("a-question") + .setMaxLength(3) + .setType(Questionnaire.QuestionnaireItemType.INTEGER) + .setText("Age in years?") + .addItem( + Questionnaire.QuestionnaireItemComponent() + .setLinkId("a-nested-question") + .setMaxLength(3) + .setType(Questionnaire.QuestionnaireItemType.STRING) + .setText("Country code") + ) + ) + val questionnaireResponse = + QuestionnaireResponse() + .addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .setLinkId("a-question") + .setAnswer( + listOf( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(IntegerType(1000)) + .addItem( + QuestionnaireResponse.QuestionnaireResponseItemComponent() + .setLinkId("a-nested-question") + .setAnswer( + listOf( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent() + .setValue(StringType("ABCD")) + ) + ) + ) + ) + ) + ) + val result = + QuestionnaireResponseValidator.validate(questionnaire.item, questionnaireResponse.item) + assertThat(result.get("a-question")) + .containsExactly( + ValidationResult( + false, + listOf("The maximum number of characters that are permitted in the answer is: 3") + ) + ) + assertThat(result.get("a-nested-question")) + .containsExactly( + ValidationResult( + false, + listOf("The maximum number of characters that are permitted in the answer is: 3") + ) + ) + } +}