diff --git a/catalog/src/main/assets/behavior_questionnaire_constraint.json b/catalog/src/main/assets/behavior_questionnaire_constraint.json
new file mode 100644
index 0000000000..a592ccb466
--- /dev/null
+++ b/catalog/src/main/assets/behavior_questionnaire_constraint.json
@@ -0,0 +1,86 @@
+{
+ "resourceType": "Questionnaire",
+ "item": [
+ {
+ "linkId": "1",
+ "text": "Password",
+ "type": "string",
+ "item": [
+ {
+ "extension": [
+ {
+ "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
+ "valueCodeableConcept": {
+ "coding": [
+ {
+ "system": "http://hl7.org/fhir/questionnaire-display-category",
+ "code": "instructions"
+ }
+ ]
+ }
+ }
+ ],
+ "linkId": "1.1",
+ "text": "Fill the password first",
+ "type": "display"
+ }
+ ]
+ },
+ {
+ "extension": [
+ {
+ "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-constraint",
+ "extension": [
+ {
+ "url": "key",
+ "valueId": "constraint-1"
+ },
+ {
+ "url": "requirements",
+ "valueString": "Confirm password field must have the same value as password field"
+ },
+ {
+ "url": "severity",
+ "valueCode": "error"
+ },
+ {
+ "url": "expression",
+ "valueString": "%context.answer.value = %resource.descendants().where(linkId='1').answer.value"
+ },
+ {
+ "url": "human",
+ "valueString": "Password does not match"
+ },
+ {
+ "url": "location",
+ "valueString": "1"
+ }
+ ]
+ }
+ ],
+ "linkId": "2",
+ "text": "Confirm password",
+ "type": "string",
+ "item": [
+ {
+ "extension": [
+ {
+ "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
+ "valueCodeableConcept": {
+ "coding": [
+ {
+ "system": "http://hl7.org/fhir/questionnaire-display-category",
+ "code": "instructions"
+ }
+ ]
+ }
+ }
+ ],
+ "linkId": "2.1",
+ "text": "Show error message if confirm password does not match with password",
+ "type": "display"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt
index f7e7f10232..e5cbe1618d 100644
--- a/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt
+++ b/catalog/src/main/java/com/google/android/fhir/catalog/BehaviorListViewModel.kt
@@ -63,6 +63,11 @@ class BehaviorListViewModel(application: Application) : AndroidViewModel(applica
R.string.behavior_name_dynamic_question_text,
"behavior_dynamic_question_text.json",
),
+ QUESTIONNAIRE_CONSTRAINT(
+ R.drawable.ic_rule,
+ R.string.behavior_name_questionnaire_constraint,
+ "behavior_questionnaire_constraint.json",
+ ),
}
fun isBehavior(context: Context, title: String) =
diff --git a/catalog/src/main/res/drawable/ic_rule.xml b/catalog/src/main/res/drawable/ic_rule.xml
new file mode 100644
index 0000000000..5f9363d680
--- /dev/null
+++ b/catalog/src/main/res/drawable/ic_rule.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/catalog/src/main/res/layout/behavior_list_fragment.xml b/catalog/src/main/res/layout/behavior_list_fragment.xml
index f0f2979d60..0d06e5cb0c 100644
--- a/catalog/src/main/res/layout/behavior_list_fragment.xml
+++ b/catalog/src/main/res/layout/behavior_list_fragment.xml
@@ -11,6 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/component_horizontal_margin"
+ android:layout_marginBottom="@dimen/bottom_navigation_view_height"
/>
diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml
index 5509e201a6..8934420572 100644
--- a/catalog/src/main/res/values/strings.xml
+++ b/catalog/src/main/res/values/strings.xml
@@ -52,6 +52,9 @@
Dynamic question text
+ Questionnaire constraint
Initial Value
Type?,
- ): Result
-
- /**
- * The validation result containing whether the answer is valid and any error message if it is not
- * valid.
- */
- data class Result(val isValid: Boolean, val errorMessage: String?)
+ ): ConstraintValidator.Result
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerExtensionConstraintValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerExtensionConstraintValidator.kt
index 2d8397b912..0bf32d1a06 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerExtensionConstraintValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerExtensionConstraintValidator.kt
@@ -50,7 +50,7 @@ internal open class AnswerExtensionConstraintValidator(
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
context: Context,
expressionEvaluator: suspend (Expression) -> Type?,
- ): AnswerConstraintValidator.Result {
+ ): ConstraintValidator.Result {
if (questionnaireItem.hasExtension(url)) {
val extension = questionnaireItem.getExtensionByUrl(url)
val extensionValue =
@@ -61,12 +61,12 @@ internal open class AnswerExtensionConstraintValidator(
if (
extensionValue.hasValue() && answer.value.hasValue() && predicate(extensionValue, answer)
) {
- return AnswerConstraintValidator.Result(
+ return ConstraintValidator.Result(
false,
messageGenerator(extensionValue, context),
)
}
}
- return AnswerConstraintValidator.Result(true, null)
+ return ConstraintValidator.Result(true, null)
}
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintItemExtensionValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintItemExtensionValidator.kt
new file mode 100644
index 0000000000..940fb0d73f
--- /dev/null
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintItemExtensionValidator.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022-2024 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.content.Context
+import com.google.android.fhir.datacapture.extensions.ConstraintSeverityTypes
+import com.google.android.fhir.datacapture.extensions.EXTENSION_QUESTIONNAIRE_CONSTRAINT_EXPRESSION
+import com.google.android.fhir.datacapture.extensions.EXTENSION_QUESTIONNAIRE_CONSTRAINT_HUMAN
+import com.google.android.fhir.datacapture.extensions.EXTENSION_QUESTIONNAIRE_CONSTRAINT_SEVERITY
+import com.google.android.fhir.datacapture.extensions.EXTENSION_QUESTIONNAIRE_CONSTRAINT_URL
+import com.google.android.fhir.datacapture.extensions.asStringValue
+import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator
+import com.google.android.fhir.datacapture.fhirpath.convertToBoolean
+import org.hl7.fhir.r4.model.CodeType
+import org.hl7.fhir.r4.model.Expression
+import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
+import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent
+
+/**
+ * TODO: Add constraint support for global case, create a separate validator,
+ * https://github.com/google/android-fhir/issues/2479
+ */
+internal class ConstraintItemExtensionValidator(
+ private val expressionEvaluator: ExpressionEvaluator,
+) : QuestionnaireResponseItemConstraintValidator {
+ override suspend fun validate(
+ questionnaireItem: QuestionnaireItemComponent,
+ questionnaireResponseItem: QuestionnaireResponseItemComponent,
+ context: Context,
+ ): List {
+ return questionnaireItem.extension
+ .filter { extension ->
+ /**
+ * TODO: Add constraint support for warning case, update the [ConstraintValidator.Result]
+ * data class to also include warning state,
+ * https://github.com/google/android-fhir/issues/2480
+ */
+ extension.url == EXTENSION_QUESTIONNAIRE_CONSTRAINT_URL &&
+ (extension.getExtensionByUrl(EXTENSION_QUESTIONNAIRE_CONSTRAINT_SEVERITY).value
+ as CodeType)
+ .valueAsString == ConstraintSeverityTypes.ERROR.code
+ }
+ .map { extension ->
+ val expression =
+ Expression().apply {
+ language = "text/fhirpath"
+ expression =
+ extension
+ .getExtensionByUrl(EXTENSION_QUESTIONNAIRE_CONSTRAINT_EXPRESSION)
+ .value
+ .asStringValue()
+ }
+ val isValid =
+ expressionEvaluator
+ .evaluateExpression(
+ questionnaireItem,
+ questionnaireResponseItem,
+ expression,
+ )
+ .let { convertToBoolean(it) }
+ if (isValid) {
+ ConstraintValidator.Result(true, null)
+ } else {
+ val errorMessage =
+ extension
+ .getExtensionByUrl(EXTENSION_QUESTIONNAIRE_CONSTRAINT_HUMAN)
+ .value
+ .asStringValue()
+ ConstraintValidator.Result(false, errorMessage)
+ }
+ }
+ }
+}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintValidator.kt
new file mode 100644
index 0000000000..7d1ae69979
--- /dev/null
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/ConstraintValidator.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022-2024 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
+
+/** Validator base interface. */
+internal interface ConstraintValidator {
+
+ /**
+ * The validation result containing whether the response item is valid and any error message if it
+ * is not valid.
+ */
+ data class Result(val isValid: Boolean, val errorMessage: String?)
+}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxLengthValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxLengthValidator.kt
index 0d3fb6aa55..51e35674bb 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxLengthValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxLengthValidator.kt
@@ -35,18 +35,18 @@ internal object MaxLengthValidator : AnswerConstraintValidator {
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
context: Context,
expressionEvaluator: suspend (Expression) -> Type?,
- ): AnswerConstraintValidator.Result {
+ ): ConstraintValidator.Result {
if (
questionnaireItem.hasMaxLength() &&
answer.value.isPrimitive &&
answer.value.asStringValue().length > questionnaireItem.maxLength
) {
- return AnswerConstraintValidator.Result(
+ return ConstraintValidator.Result(
false,
"The maximum number of characters that are permitted in the answer is: " +
questionnaireItem.maxLength,
)
}
- return AnswerConstraintValidator.Result(true, null)
+ return ConstraintValidator.Result(true, null)
}
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemConstraintValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemConstraintValidator.kt
index c2cb275333..828a16af84 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemConstraintValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/QuestionnaireResponseItemConstraintValidator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2023 Google LLC
+ * Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,26 +24,20 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse
* Validates [QuestionnaireResponse.QuestionnaireResponseItemComponent] against a particular
* constraint.
*/
-internal interface QuestionnaireResponseItemConstraintValidator {
+internal interface QuestionnaireResponseItemConstraintValidator : ConstraintValidator {
/**
- * Validates that [answers] satisfy a particular constraint of the [questionnaireItem] according
- * to the [structured data capture implementation guide]
+ * Validates that [questionnaireResponseItem] satisfy a particular constraint of the
+ * [questionnaireItem] according to the [structured data capture implementation guide]
* (http://build.fhir.org/ig/HL7/sdc/behavior.html).
*
- * This does not validate the consistency between the structure of the [answers] and their
- * descendants and that of the [questionnaireItem] and its descendants.
+ * This does not validate the consistency between the structure of the [questionnaireResponseItem]
+ * and their descendants and that of the [questionnaireItem] and its descendants.
*
* [Learn more](https://www.hl7.org/fhir/questionnaireresponse.html#link).
*/
- fun validate(
+ suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
- answers: List,
+ questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context,
- ): Result
-
- /**
- * The validation result containing whether the response item is valid and any error message if it
- * is not valid.
- */
- data class Result(val isValid: Boolean, val errorMessage: String?)
+ ): List
}
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 7a386e48b2..bdd2fbea78 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
@@ -18,17 +18,19 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import com.google.android.fhir.datacapture.extensions.isHidden
-import org.hl7.fhir.r4.model.Expression
+import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
-import org.hl7.fhir.r4.model.Type
-internal object QuestionnaireResponseItemValidator {
+internal class QuestionnaireResponseItemValidator(
+ val expressionEvaluator: ExpressionEvaluator,
+) {
/** Validators for [QuestionnaireResponse.QuestionnaireResponseItemComponent]. */
private val questionnaireResponseItemConstraintValidators =
listOf(
RequiredValidator,
+ ConstraintItemExtensionValidator(expressionEvaluator),
)
/** Validators for [QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent]. */
@@ -42,23 +44,28 @@ internal object QuestionnaireResponseItemValidator {
RegexValidator,
)
- /** Validates [answers] contains valid answer(s) to [questionnaireItem]. */
+ /** Validates [questionnaireResponseItem] contains valid answer(s) to [questionnaireItem]. */
suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
- answers: List,
+ questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context,
- expressionEvaluator: suspend (Expression) -> Type?,
): ValidationResult {
if (questionnaireItem.isHidden) return NotValidated
val questionnaireResponseItemConstraintValidationResult =
- questionnaireResponseItemConstraintValidators.map {
- it.validate(questionnaireItem, answers, context)
+ questionnaireResponseItemConstraintValidators.flatMap {
+ it.validate(questionnaireItem, questionnaireResponseItem, context)
}
val questionnaireResponseItemAnswerConstraintValidationResult =
answerConstraintValidators.flatMap { validator ->
- answers.map { answer ->
- validator.validate(questionnaireItem, answer, context, expressionEvaluator)
+ questionnaireResponseItem.answer.map { answer ->
+ validator.validate(questionnaireItem, answer, context) {
+ expressionEvaluator.evaluateExpressionValue(
+ questionnaireItem,
+ questionnaireResponseItem,
+ it,
+ )
+ }
}
}
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
index ec449b36d9..fce6d84d0e 100644
--- 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
@@ -71,25 +71,31 @@ object QuestionnaireResponseValidator {
"Mismatching Questionnaire ${questionnaire.url} and QuestionnaireResponse (for Questionnaire ${questionnaireResponse.questionnaire})"
}
- val linkIdToValidationResultMap = mutableMapOf>()
-
- validateQuestionnaireResponseItems(
- questionnaire.item,
- questionnaireResponse.item,
- context,
+ val enablementEvaluator =
EnablementEvaluator(
questionnaire,
questionnaireResponse,
questionnaireItemParentMap,
launchContextMap,
xFhirQueryResolver,
- ),
- ExpressionEvaluator(
- questionnaire,
- questionnaireResponse,
- questionnaireItemParentMap,
- launchContextMap,
- ),
+ )
+ val questionnaireResponseItemValidator =
+ QuestionnaireResponseItemValidator(
+ ExpressionEvaluator(
+ questionnaire,
+ questionnaireResponse,
+ questionnaireItemParentMap,
+ launchContextMap,
+ ),
+ )
+ val linkIdToValidationResultMap = mutableMapOf>()
+
+ validateQuestionnaireResponseItems(
+ questionnaire.item,
+ questionnaireResponse.item,
+ context,
+ enablementEvaluator,
+ questionnaireResponseItemValidator,
linkIdToValidationResultMap,
)
@@ -101,7 +107,7 @@ object QuestionnaireResponseValidator {
questionnaireResponseItemList: List,
context: Context,
enablementEvaluator: EnablementEvaluator,
- expressionEvaluator: ExpressionEvaluator,
+ questionnaireResponseItemValidator: QuestionnaireResponseItemValidator,
linkIdToValidationResultMap: MutableMap>,
): Map> {
val questionnaireItemListIterator = questionnaireItemList.iterator()
@@ -129,7 +135,7 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem,
context,
enablementEvaluator,
- expressionEvaluator,
+ questionnaireResponseItemValidator,
linkIdToValidationResultMap,
)
}
@@ -142,7 +148,7 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context,
enablementEvaluator: EnablementEvaluator,
- expressionEvaluator: ExpressionEvaluator,
+ questionnaireResponseItemValidator: QuestionnaireResponseItemValidator,
linkIdToValidationResultMap: MutableMap>,
): Map> {
when (checkNotNull(questionnaireItem.type) { "Questionnaire item must have type" }) {
@@ -156,7 +162,7 @@ object QuestionnaireResponseValidator {
questionnaireResponseItem.item,
context,
enablementEvaluator,
- expressionEvaluator,
+ questionnaireResponseItemValidator,
linkIdToValidationResultMap,
)
else -> {
@@ -170,24 +176,18 @@ object QuestionnaireResponseValidator {
it.item,
context,
enablementEvaluator,
- expressionEvaluator,
+ questionnaireResponseItemValidator,
linkIdToValidationResultMap,
)
}
linkIdToValidationResultMap[questionnaireItem.linkId] = mutableListOf()
linkIdToValidationResultMap[questionnaireItem.linkId]?.add(
- QuestionnaireResponseItemValidator.validate(
+ questionnaireResponseItemValidator.validate(
questionnaireItem,
- questionnaireResponseItem.answer,
+ questionnaireResponseItem,
context,
- ) {
- expressionEvaluator.evaluateExpressionValue(
- questionnaireItem,
- questionnaireResponseItem,
- it,
- )
- },
+ ),
)
}
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RequiredValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RequiredValidator.kt
index 87cad9c630..fabdeb9a45 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RequiredValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RequiredValidator.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2023 Google LLC
+ * Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,17 +22,19 @@ import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
internal object RequiredValidator : QuestionnaireResponseItemConstraintValidator {
- override fun validate(
+ override suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
- answers: List,
+ questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
context: Context,
- ): QuestionnaireResponseItemConstraintValidator.Result {
- if (!questionnaireItem.required || answers.any { it.hasValue() }) {
- return QuestionnaireResponseItemConstraintValidator.Result(true, null)
+ ): List {
+ if (!questionnaireItem.required || questionnaireResponseItem.answer.any { it.hasValue() }) {
+ return listOf(ConstraintValidator.Result(true, null))
}
- return QuestionnaireResponseItemConstraintValidator.Result(
- false,
- context.getString(R.string.required_constraint_validation_error_msg),
+ return listOf(
+ ConstraintValidator.Result(
+ false,
+ context.getString(R.string.required_constraint_validation_error_msg),
+ ),
)
}
}
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 a0219d3f27..47b71e1553 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
@@ -19,12 +19,14 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
+import com.google.android.fhir.datacapture.fhirpath.ExpressionEvaluator
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.Extension
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.QuestionnaireResponse.QuestionnaireResponseItemComponent
import org.hl7.fhir.r4.model.StringType
import org.junit.Before
import org.junit.Test
@@ -72,15 +74,26 @@ class QuestionnaireResponseItemValidatorTest {
value = IntegerType(275)
},
)
+ val questionnaire = Questionnaire().apply { addItem(questionnaireItem) }
+ val questionnaireResponse =
+ QuestionnaireResponse().apply {
+ addItem(
+ QuestionnaireResponseItemComponent().apply { answer = answers },
+ )
+ }
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ questionnaire,
+ questionnaireResponse,
+ )
val validationResult =
- QuestionnaireResponseItemValidator.validate(
- questionnaireItem,
- answers,
- context,
- ) {
- TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
- }
+ QuestionnaireResponseItemValidator(expressionEvaluator)
+ .validate(
+ questionnaire.item.first(),
+ questionnaireResponse.item.first(),
+ context,
+ )
assertThat(validationResult).isEqualTo(Valid)
}
@@ -116,15 +129,26 @@ class QuestionnaireResponseItemValidatorTest {
value = IntegerType(250)
},
)
+ val questionnaire = Questionnaire().apply { addItem(questionnaireItem) }
+ val questionnaireResponse =
+ QuestionnaireResponse().apply {
+ addItem(
+ QuestionnaireResponseItemComponent().apply { answer = answers },
+ )
+ }
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ questionnaire,
+ questionnaireResponse,
+ )
val validationResult =
- QuestionnaireResponseItemValidator.validate(
- questionnaireItem,
- answers,
- context,
- ) {
- TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
- }
+ QuestionnaireResponseItemValidator(expressionEvaluator)
+ .validate(
+ questionnaire.item.first(),
+ questionnaireResponse.item.first(),
+ context,
+ )
assertThat(validationResult).isInstanceOf(Invalid::class.java)
val invalidValidationResult = validationResult as Invalid
@@ -140,15 +164,27 @@ class QuestionnaireResponseItemValidatorTest {
required = true
}
val answers = listOf()
+ val questionnaire = Questionnaire().apply { addItem(questionnaireItem) }
+ val questionnaireResponse =
+ QuestionnaireResponse().apply {
+ addItem(
+ QuestionnaireResponseItemComponent().apply { answer = answers },
+ )
+ }
+
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ questionnaire,
+ questionnaireResponse,
+ )
val validationResult =
- QuestionnaireResponseItemValidator.validate(
- questionnaireItem,
- answers,
- context,
- ) {
- TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
- }
+ QuestionnaireResponseItemValidator(expressionEvaluator)
+ .validate(
+ questionnaire.item.first(),
+ questionnaireResponse.item.first(),
+ context,
+ )
assertThat(validationResult).isInstanceOf(Invalid::class.java)
val invalidValidationResult = validationResult as Invalid
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RequiredValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RequiredValidatorTest.kt
index 4f6b500cc4..cbb021d9b7 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RequiredValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RequiredValidatorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2022-2023 Google LLC
+ * Copyright 2022-2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.validation
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
@@ -33,55 +34,71 @@ class RequiredValidatorTest {
@Test
fun shouldReturnValidResult() {
- val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
- val response =
- listOf(
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
- value = IntegerType(9)
- },
- )
+ runTest {
+ val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
+ val questionnaireResponseItem =
+ QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
+ answer =
+ listOf(
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value = IntegerType(9)
+ },
+ )
+ }
- val validationResult =
- RequiredValidator.validate(
- questionnaireItem,
- response,
- InstrumentationRegistry.getInstrumentation().context,
- )
- assertThat(validationResult.isValid).isTrue()
+ val validationResult =
+ RequiredValidator.validate(
+ questionnaireItem,
+ questionnaireResponseItem,
+ InstrumentationRegistry.getInstrumentation().context,
+ )
+ assertThat(validationResult.first().isValid).isTrue()
+ }
}
@Test
fun shouldReturnInvalidResult() {
- val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
+ runTest {
+ val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
+ val questionnaireResponseItem =
+ QuestionnaireResponse.QuestionnaireResponseItemComponent().apply { answer = listOf() }
- val validationResult =
- RequiredValidator.validate(
- questionnaireItem,
- listOf(),
- InstrumentationRegistry.getInstrumentation().context,
- )
- assertThat(validationResult.isValid).isFalse()
- assertThat(validationResult.errorMessage).isEqualTo("Missing answer for required field.")
+ val validationResult =
+ RequiredValidator.validate(
+ questionnaireItem,
+ questionnaireResponseItem,
+ InstrumentationRegistry.getInstrumentation().context,
+ )
+ assertThat(validationResult.first().isValid).isFalse()
+ assertThat(validationResult.first().errorMessage)
+ .isEqualTo("Missing answer for required field.")
+ }
}
@Test
fun noAnswerHasValue_shouldReturnInvalidResult() {
- val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
- val response =
- listOf(
- // one answer with no value
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent(),
- // second answer with no value
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent(),
- )
+ runTest {
+ val questionnaireItem = Questionnaire.QuestionnaireItemComponent().apply { required = true }
+ val questionnaireResponseItem =
+ QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
+ answer =
+ listOf(
+ // one answer with no value
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent(),
+ // second answer with no value
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent(),
+ )
+ }
- val validationResult =
- RequiredValidator.validate(
- questionnaireItem,
- response,
- InstrumentationRegistry.getInstrumentation().context,
- )
- assertThat(validationResult.isValid).isFalse()
- assertThat(validationResult.errorMessage).isEqualTo("Missing answer for required field.")
+ val validationResult =
+ RequiredValidator.validate(
+ questionnaireItem,
+ questionnaireResponseItem,
+ InstrumentationRegistry.getInstrumentation().context,
+ )
+ assertThat(validationResult.first().isValid).isFalse()
+ assertThat(validationResult.first().errorMessage)
+ .isEqualTo("Missing answer for required field.")
+ }
}
}