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 01ba3531be..f5573a33be 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
@@ -72,8 +72,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.launch
-import org.hl7.fhir.r4.model.Base
-import org.hl7.fhir.r4.model.Element
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
import org.hl7.fhir.r4.model.QuestionnaireResponse
@@ -735,11 +733,11 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
questionnaireItem,
questionnaireResponseItem.answer,
this@QuestionnaireViewModel.getApplication(),
- ) { _, expression ->
+ ) {
expressionEvaluator.evaluateExpressionValue(
questionnaireItem,
questionnaireResponseItem,
- expression,
+ it,
)
}
} else {
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluator.kt
index 61be1b7f93..2a224a10b6 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluator.kt
@@ -142,7 +142,7 @@ internal class ExpressionEvaluator(
}
/** Returns the evaluation result of an expression as a [Type] value */
- fun evaluateExpressionValue(
+ suspend fun evaluateExpressionValue(
questionnaireItem: QuestionnaireItemComponent,
questionnaireResponseItem: QuestionnaireResponseItemComponent?,
expression: Expression,
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtil.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtil.kt
index 4c7d626913..1e86e4b8fd 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtil.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtil.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,7 +24,6 @@ import org.hl7.fhir.r4.model.ExpressionNode
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent
import org.hl7.fhir.r4.model.Resource
-import org.hl7.fhir.r4.model.Type
import org.hl7.fhir.r4.utils.FHIRPathEngine
private val fhirPathEngine: FHIRPathEngine =
@@ -101,9 +100,9 @@ internal fun evaluateToBase(
}
/** Evaluates the given expression and returns list of [Base] */
-internal fun evaluateToBase(type: Type, expression: String): List {
+internal fun evaluateToBase(base: Base, expression: String): List {
return fhirPathEngine.evaluate(
- /* base = */ type,
+ /* base = */ base,
/* path = */ expression,
)
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerConstraintValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerConstraintValidator.kt
index c73094790d..20759607af 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerConstraintValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/AnswerConstraintValidator.kt
@@ -18,7 +18,6 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import org.hl7.fhir.r4.model.Expression
-import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Type
@@ -38,11 +37,11 @@ internal interface AnswerConstraintValidator {
*
* [Learn more](https://www.hl7.org/fhir/questionnaireresponse.html#link).
*/
- fun validate(
+ suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
context: Context,
- evaluateExtensionCqfCalculatedValue: (Extension, Expression) -> Type?,
+ expressionEvaluator: suspend (Expression) -> Type?,
): 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 1d85275fc7..5e5ed9734d 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
@@ -20,7 +20,6 @@ import android.content.Context
import com.google.android.fhir.datacapture.extensions.cqfCalculatedValueExpression
import com.google.android.fhir.datacapture.extensions.hasValue
import org.hl7.fhir.r4.model.Expression
-import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Type
@@ -40,38 +39,31 @@ internal open class AnswerExtensionConstraintValidator(
val url: String,
val predicate:
(
- /*extensionValue*/
+ /*constraintValue*/
Type,
- /*answer*/
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
) -> Boolean,
val messageGenerator: (Type, Context) -> String,
) : AnswerConstraintValidator {
- override fun validate(
+ override suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
context: Context,
- evaluateExtensionCqfCalculatedValue: (Extension, Expression) -> Type?,
+ expressionEvaluator: suspend (Expression) -> Type?,
): AnswerConstraintValidator.Result {
if (questionnaireItem.hasExtension(url)) {
val extension = questionnaireItem.getExtensionByUrl(url)
- val extensionValueType =
- extension.value.let {
- it.cqfCalculatedValueExpression?.let { expression ->
- evaluateExtensionCqfCalculatedValue(extension, expression)
- }
- ?: it
- }
+ val extensionCalculatedValue =
+ extension.value.cqfCalculatedValueExpression?.let { expressionEvaluator(it) }
+ val extensionValue = extensionCalculatedValue ?: extension.value
// Only checks constraint if both extension and answer have a value
if (
- extensionValueType.hasValue() &&
- answer.value.hasValue() &&
- predicate(extensionValueType, answer)
+ extensionValue.hasValue() && answer.value.hasValue() && predicate(extensionValue, answer)
) {
return AnswerConstraintValidator.Result(
false,
- messageGenerator(extensionValueType, context),
+ messageGenerator(extensionValue, context),
)
}
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidator.kt
index 4b08c5b685..fdfc088be6 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidator.kt
@@ -33,16 +33,16 @@ internal object MaxDecimalPlacesValidator :
AnswerExtensionConstraintValidator(
url = MAX_DECIMAL_URL,
predicate = {
- extensionValue: Type,
+ constraintValue: Type,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
->
- val maxDecimalPlaces = (extensionValue as? IntegerType)?.value
+ val maxDecimalPlaces = (constraintValue as? IntegerType)?.value
answer.hasValueDecimalType() &&
maxDecimalPlaces != null &&
answer.valueDecimalType.valueAsString.substringAfter(".").length > maxDecimalPlaces
},
- messageGenerator = { extensionValue: Type, context: Context ->
- context.getString(R.string.max_decimal_validation_error_msg, extensionValue.primitiveValue())
+ messageGenerator = { constraintValue: Type, context: Context ->
+ context.getString(R.string.max_decimal_validation_error_msg, constraintValue.primitiveValue())
},
)
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 09c9b365e3..0d3fb6aa55 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
@@ -19,7 +19,6 @@ package com.google.android.fhir.datacapture.validation
import android.content.Context
import com.google.android.fhir.datacapture.extensions.asStringValue
import org.hl7.fhir.r4.model.Expression
-import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Type
@@ -31,11 +30,11 @@ import org.hl7.fhir.r4.model.Type
* https://www.hl7.org/fhir/valueset-item-type.html#expansion
*/
internal object MaxLengthValidator : AnswerConstraintValidator {
- override fun validate(
+ override suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
context: Context,
- evaluateExtensionCqfCalculatedValue: (Extension, Expression) -> Type?,
+ expressionEvaluator: suspend (Expression) -> Type?,
): AnswerConstraintValidator.Result {
if (
questionnaireItem.hasMaxLength() &&
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxValueValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxValueValidator.kt
index 5f81a764f3..3bd12d4788 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxValueValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MaxValueValidator.kt
@@ -30,15 +30,15 @@ internal object MaxValueValidator :
AnswerExtensionConstraintValidator(
url = MAX_VALUE_EXTENSION_URL,
predicate = {
- extensionValue: Type,
+ constraintValue: Type,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
->
- answer.value > extensionValue
+ answer.value > constraintValue
},
- messageGenerator = { extensionValue: Type, context: Context ->
+ messageGenerator = { constraintValue: Type, context: Context ->
context.getString(
R.string.max_value_validation_error_msg,
- extensionValue.getValueAsString(context),
+ constraintValue.getValueAsString(context),
)
},
)
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinLengthValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinLengthValidator.kt
index cc9a9741b0..623554b2f9 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinLengthValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinLengthValidator.kt
@@ -37,13 +37,13 @@ import org.hl7.fhir.r4.model.Type
internal object MinLengthValidator :
AnswerExtensionConstraintValidator(
url = MIN_LENGTH_EXTENSION_URL,
- predicate = { extensionValue, answer ->
+ predicate = { constraintValue, answer ->
answer.value.isPrimitive &&
(answer.value as PrimitiveType<*>).asStringValue().length <
- (extensionValue as IntegerType).value
+ (constraintValue as IntegerType).value
},
- messageGenerator = { extensionValue: Type, context: Context ->
- context.getString(R.string.min_length_validation_error_msg, extensionValue.primitiveValue())
+ messageGenerator = { constraintValue: Type, context: Context ->
+ context.getString(R.string.min_length_validation_error_msg, constraintValue.primitiveValue())
},
)
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinValueValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinValueValidator.kt
index 6fd92ed65e..5cc851e394 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinValueValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/MinValueValidator.kt
@@ -30,15 +30,15 @@ internal object MinValueValidator :
AnswerExtensionConstraintValidator(
url = MIN_VALUE_EXTENSION_URL,
predicate = {
- extensionValue: Type,
+ constraintValue: Type,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
->
- answer.value < extensionValue
+ answer.value < constraintValue
},
- messageGenerator = { extensionValue: Type, context: Context ->
+ messageGenerator = { constraintValue: Type, context: Context ->
context.getString(
R.string.min_value_validation_error_msg,
- extensionValue.getValueAsString(context),
+ constraintValue.getValueAsString(context),
)
},
)
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 9af989cada..7a386e48b2 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
@@ -19,7 +19,6 @@ 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 org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Type
@@ -44,11 +43,11 @@ internal object QuestionnaireResponseItemValidator {
)
/** Validates [answers] contains valid answer(s) to [questionnaireItem]. */
- fun validate(
+ suspend fun validate(
questionnaireItem: Questionnaire.QuestionnaireItemComponent,
answers: List,
context: Context,
- expressionValueEvaluator: (Extension, Expression) -> Type?,
+ expressionEvaluator: suspend (Expression) -> Type?,
): ValidationResult {
if (questionnaireItem.isHidden) return NotValidated
@@ -59,9 +58,7 @@ internal object QuestionnaireResponseItemValidator {
val questionnaireResponseItemAnswerConstraintValidationResult =
answerConstraintValidators.flatMap { validator ->
answers.map { answer ->
- validator.validate(questionnaireItem, answer, context) { extension, expression ->
- expressionValueEvaluator.invoke(extension, expression)
- }
+ validator.validate(questionnaireItem, answer, context, expressionEvaluator)
}
}
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 3fa95e3d91..ec449b36d9 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
@@ -181,11 +181,11 @@ object QuestionnaireResponseValidator {
questionnaireItem,
questionnaireResponseItem.answer,
context,
- ) { _, expression ->
+ ) {
expressionEvaluator.evaluateExpressionValue(
questionnaireItem,
questionnaireResponseItem,
- expression,
+ it,
)
},
)
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RegexValidator.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RegexValidator.kt
index 04a06cad08..97d1a6f56d 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RegexValidator.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/validation/RegexValidator.kt
@@ -37,22 +37,22 @@ internal object RegexValidator :
url = REGEX_EXTENSION_URL,
predicate =
predicate@{
- extensionValue: Type,
+ constraintValue: Type,
answer: QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent,
->
- if (!extensionValue.isPrimitive || !answer.value.isPrimitive) {
+ if (!constraintValue.isPrimitive || !answer.value.isPrimitive) {
return@predicate false
}
try {
- val pattern = Pattern.compile((extensionValue as PrimitiveType<*>).asStringValue())
+ val pattern = Pattern.compile((constraintValue as PrimitiveType<*>).asStringValue())
!pattern.matcher(answer.value.asStringValue()).matches()
} catch (e: PatternSyntaxException) {
- Timber.w("Can't parse regex: $extensionValue", e)
+ Timber.w("Can't parse regex: $constraintValue", e)
false
}
},
- messageGenerator = { extensionValue: Type, context: Context ->
- context.getString(R.string.regex_validation_error_msg, extensionValue.primitiveValue())
+ messageGenerator = { constraintValue: Type, context: Context ->
+ context.getString(R.string.regex_validation_error_msg, constraintValue.primitiveValue())
},
)
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidatorTest.kt
index aba8340b0f..52b3f9f1ab 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxDecimalPlacesValidatorTest.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.Extension
import org.hl7.fhir.r4.model.IntegerType
@@ -42,15 +43,16 @@ class MaxDecimalPlacesValidatorTest {
}
@Test
- fun validate_noExtension_shouldReturnValidResult() {
+ fun validate_noExtension_shouldReturnValidResult() = runTest {
+ val questionnaireItem = Questionnaire.QuestionnaireItemComponent()
val validationResult =
MaxDecimalPlacesValidator.validate(
- Questionnaire.QuestionnaireItemComponent(),
+ questionnaireItem,
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(DecimalType("1.00")),
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -58,17 +60,19 @@ class MaxDecimalPlacesValidatorTest {
}
@Test
- fun validate_validAnswer_shouldReturnValidResult() {
+ fun validate_validAnswer_shouldReturnValidResult() = runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ this.addExtension(Extension(MAX_DECIMAL_URL, IntegerType(2)))
+ }
val validationResult =
MaxDecimalPlacesValidator.validate(
- Questionnaire.QuestionnaireItemComponent().apply {
- this.addExtension(Extension(MAX_DECIMAL_URL, IntegerType(2)))
- },
+ questionnaireItem,
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(DecimalType("1.00")),
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -76,17 +80,19 @@ class MaxDecimalPlacesValidatorTest {
}
@Test
- fun validate_tooManyDecimalPlaces_shouldReturnInvalidResult() {
+ fun validate_tooManyDecimalPlaces_shouldReturnInvalidResult() = runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ this.addExtension(Extension(MAX_DECIMAL_URL, IntegerType(2)))
+ }
val validationResult =
MaxDecimalPlacesValidator.validate(
- Questionnaire.QuestionnaireItemComponent().apply {
- this.addExtension(Extension(MAX_DECIMAL_URL, IntegerType(2)))
- },
+ questionnaireItem,
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent()
.setValue(DecimalType("1.000")),
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isFalse()
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxLengthValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxLengthValidatorTest.kt
index 58d56ec773..cb9edda933 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxLengthValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxLengthValidatorTest.kt
@@ -22,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import java.net.URI
import java.text.SimpleDateFormat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
@@ -51,79 +52,79 @@ class MaxLengthValidatorTest {
}
@Test
- fun boolean_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun boolean_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 4, value = BooleanType(false))
}
@Test
- fun boolean_answerUnderMaxLength_shouldReturnValidResult() {
+ fun boolean_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 6, value = BooleanType(false))
}
@Test
- fun decimal_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun decimal_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 10, value = DecimalType(3.1415926535))
}
@Test
- fun decimal_answerUnderMaxLength_shouldReturnValidResult() {
+ fun decimal_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 16, value = DecimalType(3.1415926535))
}
@Test
- fun int_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun int_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 5, value = IntegerType(1234567890))
}
@Test
- fun int_answerUnderMaxLength_shouldReturnValidResult() {
+ fun int_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 10, value = IntegerType(1234567890))
}
@Test
- fun dateType_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun dateType_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerOverMaxLength(maxLength = 5, value = DateType(dateFormat.parse("2021-06-01")))
}
@Test
- fun date_answerUnderMaxLength_shouldReturnValidResult() {
+ fun date_answerUnderMaxLength_shouldReturnValidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerUnderMaxLength(maxLength = 11, value = DateType(dateFormat.parse("2021-06-01")))
}
@Test
- fun time_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun time_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 5, value = TimeType("18:00:59"))
}
@Test
- fun time_answerUnderMaxLength_shouldReturnValidResult() {
+ fun time_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 9, value = TimeType("18:00:59"))
}
@Test
- fun string_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun string_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 5, value = StringType("Hello World"))
}
@Test
- fun string_answerUnderMaxLength_shouldReturnValidResult() {
+ fun string_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 11, value = StringType("Hello World"))
}
@Test
- fun uri_answerOverMaxLength_shouldReturnInvalidResult() {
+ fun uri_answerOverMaxLength_shouldReturnInvalidResult() = runTest {
checkAnswerOverMaxLength(maxLength = 5, value = UriType(URI.create("https://www.hl7.org/")))
}
@Test
- fun uri_answerUnderMaxLength_shouldReturnValidResult() {
+ fun uri_answerUnderMaxLength_shouldReturnValidResult() = runTest {
checkAnswerUnderMaxLength(maxLength = 20, value = UriType(URI.create("https://www.hl7.org/")))
}
@Test
- fun nonPrimitiveOverMaxLength_shouldReturnValidResult() {
+ fun nonPrimitiveOverMaxLength_shouldReturnValidResult() = runTest {
val requirement = Questionnaire.QuestionnaireItemComponent().apply { maxLength = 5 }
val answer =
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
@@ -131,8 +132,8 @@ class MaxLengthValidatorTest {
}
val validationResult =
- MaxLengthValidator.validate(requirement, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ MaxLengthValidator.validate(requirement, answer, context) {
+ TestExpressionValueEvaluator.evaluate(requirement, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -144,7 +145,7 @@ class MaxLengthValidatorTest {
var context: Context = ApplicationProvider.getApplicationContext()
@JvmStatic
- fun checkAnswerOverMaxLength(maxLength: Int, value: PrimitiveType<*>) {
+ suspend fun checkAnswerOverMaxLength(maxLength: Int, value: PrimitiveType<*>) {
val testComponent = createMaxLengthQuestionnaireTestItem(maxLength, value)
val validationResult =
@@ -152,8 +153,8 @@ class MaxLengthValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isFalse()
@@ -164,7 +165,7 @@ class MaxLengthValidatorTest {
}
@JvmStatic
- fun checkAnswerUnderMaxLength(maxLength: Int, value: PrimitiveType<*>) {
+ suspend fun checkAnswerUnderMaxLength(maxLength: Int, value: PrimitiveType<*>) {
val testComponent = createMaxLengthQuestionnaireTestItem(maxLength, value)
val validationResult =
@@ -172,8 +173,8 @@ class MaxLengthValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isTrue()
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueValidatorTest.kt
index 8c9e4df18d..2c3b1346bd 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MaxValueValidatorTest.kt
@@ -25,6 +25,7 @@ import com.google.common.truth.Truth.assertThat
import java.time.LocalDate
import java.time.ZoneId
import java.util.Date
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
@@ -49,7 +50,7 @@ class MaxValueValidatorTest {
}
@Test
- fun `should return invalid result when entered value is greater than maxValue`() {
+ fun `should return invalid result when entered value is greater than maxValue`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -65,8 +66,8 @@ class MaxValueValidatorTest {
}
val validationResult =
- MaxValueValidator.validate(questionnaireItem, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ MaxValueValidator.validate(questionnaireItem, answer, context) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isFalse()
@@ -74,7 +75,7 @@ class MaxValueValidatorTest {
}
@Test
- fun `should return valid result when entered value is less than maxValue`() {
+ fun `should return valid result when entered value is less than maxValue`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -90,8 +91,8 @@ class MaxValueValidatorTest {
}
val validationResult =
- MaxValueValidator.validate(questionnaireItem, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ MaxValueValidator.validate(questionnaireItem, answer, context) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -99,166 +100,172 @@ class MaxValueValidatorTest {
}
@Test
- fun `should return invalid result with correct max allowed value if contains only cqf-calculatedValue`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- this.url = MAX_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- addExtension(
- Extension(
- EXTENSION_CQF_CALCULATED_VALUE_URL,
- Expression().apply {
- expression = "today() - 7 'days'"
- language = "text/fhirpath"
- },
- ),
- )
- },
- )
- },
- )
- }
- val answer =
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
- value = DateType().apply { value = Date() }
- }
-
- val validationResult =
- MaxValueValidator.validate(questionnaireItem, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
-
- assertThat(validationResult.isValid).isFalse()
- assertThat(validationResult.errorMessage)
- .isEqualTo("Maximum value allowed is:${LocalDate.now().minusDays(7)}")
- }
-
- @Test
- fun `should return invalid result with correct max allowed value if contains both value and cqf-calculatedValue`() {
- val tenDaysAgo = LocalDate.now().minusDays(10)
-
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- this.url = MAX_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- value =
- Date.from(tenDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
- addExtension(
- Extension(
- EXTENSION_CQF_CALCULATED_VALUE_URL,
- Expression().apply {
- expression = "today() - 7 'days'"
- language = "text/fhirpath"
- },
- ),
- )
- },
- )
- },
- )
- }
- val answer =
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
- value = DateType().apply { value = Date() }
- }
-
- val validationResult =
- MaxValueValidator.validate(questionnaireItem, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
-
- assertThat(validationResult.isValid).isFalse()
- assertThat(validationResult.errorMessage)
- .isEqualTo("Maximum value allowed is:${LocalDate.now().minusDays(7)}")
- }
-
- @Test
- fun `should return valid result and removes constraint for an answer value when maxValue cqf-calculatedValue evaluates to empty`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- url = MAX_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- extension =
- listOf(
+ fun `should return invalid result with correct max allowed value if contains only cqf-calculatedValue`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ this.url = MAX_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ addExtension(
Extension(
EXTENSION_CQF_CALCULATED_VALUE_URL,
Expression().apply {
+ expression = "today() - 7 'days'"
language = "text/fhirpath"
- expression = "yesterday()" // invalid FHIRPath expression
},
),
)
- },
- )
- },
- )
- }
-
- val answer =
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
- value = DateType(Date())
- }
+ },
+ )
+ },
+ )
+ }
+ val answer =
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value = DateType().apply { value = Date() }
+ }
- val validationResult =
- MaxValueValidator.validate(
- questionnaireItem,
- answer,
- InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
+ val validationResult =
+ MaxValueValidator.validate(questionnaireItem, answer, context) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
- assertThat(validationResult.isValid).isTrue()
- assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
- }
+ assertThat(validationResult.isValid).isFalse()
+ assertThat(validationResult.errorMessage)
+ .isEqualTo("Maximum value allowed is:${LocalDate.now().minusDays(7)}")
+ }
@Test
- fun `should return valid result and removes constraint for an answer with an empty value`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- url = MAX_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- extension =
- listOf(
+ fun `should return invalid result with correct max allowed value if contains both value and cqf-calculatedValue`() =
+ runTest {
+ val tenDaysAgo = LocalDate.now().minusDays(10)
+
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ this.url = MAX_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ value =
+ Date.from(tenDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
+ addExtension(
Extension(
EXTENSION_CQF_CALCULATED_VALUE_URL,
Expression().apply {
+ expression = "today() - 7 'days'"
language = "text/fhirpath"
- expression = "today()"
},
),
)
- },
- )
- },
- )
- }
+ },
+ )
+ },
+ )
+ }
+ val answer =
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value = DateType().apply { value = Date() }
+ }
- val answer =
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { value = DateType() }
+ val validationResult =
+ MaxValueValidator.validate(questionnaireItem, answer, context) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
- val validationResult =
- MaxValueValidator.validate(
- questionnaireItem,
- answer,
- InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
+ assertThat(validationResult.isValid).isFalse()
+ assertThat(validationResult.errorMessage)
+ .isEqualTo("Maximum value allowed is:${LocalDate.now().minusDays(7)}")
+ }
- assertThat(validationResult.isValid).isTrue()
- assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
- }
+ @Test
+ fun `should return valid result and removes constraint for an answer value when maxValue cqf-calculatedValue evaluates to empty`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ url = MAX_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ extension =
+ listOf(
+ Extension(
+ EXTENSION_CQF_CALCULATED_VALUE_URL,
+ Expression().apply {
+ language = "text/fhirpath"
+ expression = "yesterday()" // invalid FHIRPath expression
+ },
+ ),
+ )
+ },
+ )
+ },
+ )
+ }
+
+ val answer =
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value = DateType(Date())
+ }
+
+ val validationResult =
+ MaxValueValidator.validate(
+ questionnaireItem,
+ answer,
+ InstrumentationRegistry.getInstrumentation().context,
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
+
+ assertThat(validationResult.isValid).isTrue()
+ assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
+ }
+
+ @Test
+ fun `should return valid result and removes constraint for an answer with an empty value`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ url = MAX_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ extension =
+ listOf(
+ Extension(
+ EXTENSION_CQF_CALCULATED_VALUE_URL,
+ Expression().apply {
+ language = "text/fhirpath"
+ expression = "today()"
+ },
+ ),
+ )
+ },
+ )
+ },
+ )
+ }
+
+ val answer =
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value = DateType()
+ }
+
+ val validationResult =
+ MaxValueValidator.validate(
+ questionnaireItem,
+ answer,
+ InstrumentationRegistry.getInstrumentation().context,
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
+
+ assertThat(validationResult.isValid).isTrue()
+ assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
+ }
}
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinLengthValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinLengthValidatorTest.kt
index 359f667bb3..c41ffbe49e 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinLengthValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinLengthValidatorTest.kt
@@ -22,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import java.net.URI
import java.text.SimpleDateFormat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
@@ -52,79 +53,79 @@ class MinLengthValidatorTest {
}
@Test
- fun boolean_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun boolean_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 10, value = BooleanType(false))
}
@Test
- fun boolean_answerOverMinLength_shouldReturnValidResult() {
+ fun boolean_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 5, value = BooleanType(false))
}
@Test
- fun decimal_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun decimal_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 15, value = DecimalType(3.1415926535))
}
@Test
- fun decimal_answerOverMinLength_shouldReturnValidResult() {
+ fun decimal_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 10, value = DecimalType(3.1415926535))
}
@Test
- fun int_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun int_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 5, value = IntegerType(123))
}
@Test
- fun int_answerOverMinLength_shouldReturnValidResult() {
+ fun int_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 10, value = IntegerType(1234567890))
}
@Test
- fun dateType_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun dateType_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerUnderMinLength(minLength = 11, value = DateType(dateFormat.parse("2021-06-01")))
}
@Test
- fun date_answerOverMinLength_shouldReturnValidResult() {
+ fun date_answerOverMinLength_shouldReturnValidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerOverMinLength(minLength = 10, value = DateType(dateFormat.parse("2021-06-01")))
}
@Test
- fun time_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun time_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 10, value = TimeType("18:00:59"))
}
@Test
- fun time_answerOverMinLength_shouldReturnValidResult() {
+ fun time_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 5, value = TimeType("18:00:59"))
}
@Test
- fun string_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun string_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 12, value = StringType("Hello World"))
}
@Test
- fun string_answerOverMinLength_shouldReturnValidResult() {
+ fun string_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 5, value = StringType("Hello World"))
}
@Test
- fun uri_answerUnderMinLength_shouldReturnInvalidResult() {
+ fun uri_answerUnderMinLength_shouldReturnInvalidResult() = runTest {
checkAnswerUnderMinLength(minLength = 21, value = UriType(URI.create("https://www.hl7.org/")))
}
@Test
- fun uri_answerOverMinLength_shouldReturnValidResult() {
+ fun uri_answerOverMinLength_shouldReturnValidResult() = runTest {
checkAnswerOverMinLength(minLength = 5, value = UriType(URI.create("https://www.hl7.org/")))
}
@Test
- fun nonPrimitiveUnderMinLength_shouldReturnValidResult() {
+ fun nonPrimitiveUnderMinLength_shouldReturnValidResult() = runTest {
val requirement =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -140,8 +141,8 @@ class MinLengthValidatorTest {
}
val validationResult =
- MaxLengthValidator.validate(requirement, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ MaxLengthValidator.validate(requirement, answer, context) {
+ TestExpressionValueEvaluator.evaluate(requirement, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -153,7 +154,7 @@ class MinLengthValidatorTest {
var context: Context = ApplicationProvider.getApplicationContext()
@JvmStatic
- fun checkAnswerOverMinLength(minLength: Int, value: PrimitiveType<*>) {
+ suspend fun checkAnswerOverMinLength(minLength: Int, value: PrimitiveType<*>) {
val testComponent = createMaxLengthQuestionnaireTestItem(minLength, value)
val validationResult =
@@ -161,8 +162,8 @@ class MinLengthValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -170,7 +171,7 @@ class MinLengthValidatorTest {
}
@JvmStatic
- fun checkAnswerUnderMinLength(minLength: Int, value: PrimitiveType<*>) {
+ suspend fun checkAnswerUnderMinLength(minLength: Int, value: PrimitiveType<*>) {
val testComponent = createMaxLengthQuestionnaireTestItem(minLength, value)
val validationResult =
@@ -178,8 +179,8 @@ class MinLengthValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isFalse()
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinValueValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinValueValidatorTest.kt
index 497feb3439..8622e7289e 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinValueValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/MinValueValidatorTest.kt
@@ -25,6 +25,7 @@ import com.google.common.truth.Truth.assertThat
import java.time.LocalDate
import java.time.ZoneId
import java.util.Date
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Extension
@@ -50,7 +51,7 @@ class MinValueValidatorTest {
}
@Test
- fun `should return invalid result when entered value is less than minValue`() {
+ fun `should return invalid result when entered value is less than minValue`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -67,8 +68,8 @@ class MinValueValidatorTest {
questionnaireItem,
answer,
InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isFalse()
@@ -76,7 +77,7 @@ class MinValueValidatorTest {
}
@Test
- fun `should return valid result when entered value is greater than minValue`() {
+ fun `should return valid result when entered value is greater than minValue`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -93,8 +94,8 @@ class MinValueValidatorTest {
questionnaireItem,
answer,
InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -102,172 +103,179 @@ class MinValueValidatorTest {
}
@Test
- fun `should return valid result when entered value is greater than minValue for cqf calculated expression`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- url = MIN_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- extension =
- listOf(
- Extension(
- EXTENSION_CQF_CALCULATED_VALUE_URL,
- Expression().apply {
- language = "text/fhirpath"
- expression = "today() - 1 'days'"
- },
- ),
- )
- },
- )
- },
- )
- }
+ fun `should return valid result when entered value is greater than minValue for cqf calculated expression`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ url = MIN_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ extension =
+ listOf(
+ Extension(
+ EXTENSION_CQF_CALCULATED_VALUE_URL,
+ Expression().apply {
+ language = "text/fhirpath"
+ expression = "today() - 1 'days'"
+ },
+ ),
+ )
+ },
+ )
+ },
+ )
+ }
- val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType(Date()) }
+ val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType(Date()) }
- val validationResult =
- MinValueValidator.validate(
- questionnaireItem,
- answer,
- InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
+ val validationResult =
+ MinValueValidator.validate(
+ questionnaireItem,
+ answer,
+ InstrumentationRegistry.getInstrumentation().context,
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
- assertThat(validationResult.isValid).isTrue()
- assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
- }
+ assertThat(validationResult.isValid).isTrue()
+ assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
+ }
@Test
- fun `should return invalid result with correct min allowed value if contains both value and cqf-calculatedValue`() {
- val sevenDaysAgo = LocalDate.now().minusDays(7)
-
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- this.url = MIN_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- value =
- Date.from(sevenDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
- addExtension(
- Extension(
- EXTENSION_CQF_CALCULATED_VALUE_URL,
- Expression().apply {
- expression = "today() - 3 'days'"
- language = "text/fhirpath"
- },
- ),
- )
- },
- )
- },
- )
- }
- val answer =
- QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
- value =
- DateType().apply {
- val fiveDaysAgo = LocalDate.now().minusDays(5)
- value = Date.from(fiveDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
- }
- }
+ fun `should return invalid result with correct min allowed value if contains both value and cqf-calculatedValue`() =
+ runTest {
+ val sevenDaysAgo = LocalDate.now().minusDays(7)
- val validationResult =
- MinValueValidator.validate(questionnaireItem, answer, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
-
- assertThat(validationResult.isValid).isFalse()
- assertThat(validationResult.errorMessage)
- .isEqualTo("Minimum value allowed is:${LocalDate.now().minusDays(3)}")
- }
-
- @Test
- fun `should return valid result and removes constraint for an answer value when minValue cqf-calculatedValue evaluates to empty`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- url = MIN_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- extension =
- listOf(
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ this.url = MIN_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ value =
+ Date.from(
+ sevenDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(),
+ )
+ addExtension(
Extension(
EXTENSION_CQF_CALCULATED_VALUE_URL,
Expression().apply {
+ expression = "today() - 3 'days'"
language = "text/fhirpath"
- expression = "yesterday()" // invalid FHIRPath expression
},
),
)
- },
- )
- },
- )
- }
-
- val twoDaysAgo =
- Date.from(
- LocalDate.now().minusDays(2).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(),
- )
- val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType(twoDaysAgo) }
+ },
+ )
+ },
+ )
+ }
+ val answer =
+ QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
+ value =
+ DateType().apply {
+ val fiveDaysAgo = LocalDate.now().minusDays(5)
+ value =
+ Date.from(fiveDaysAgo.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant())
+ }
+ }
- val validationResult =
- MinValueValidator.validate(
- questionnaireItem,
- answer,
- InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
+ val validationResult =
+ MinValueValidator.validate(questionnaireItem, answer, context) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
- assertThat(validationResult.isValid).isTrue()
- assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
- }
+ assertThat(validationResult.isValid).isFalse()
+ assertThat(validationResult.errorMessage)
+ .isEqualTo("Minimum value allowed is:${LocalDate.now().minusDays(3)}")
+ }
@Test
- fun `should return valid result and removes constraint for an answer with an empty value`() {
- val questionnaireItem =
- Questionnaire.QuestionnaireItemComponent().apply {
- addExtension(
- Extension().apply {
- url = MIN_VALUE_EXTENSION_URL
- this.setValue(
- DateType().apply {
- extension =
- listOf(
- Extension(
- EXTENSION_CQF_CALCULATED_VALUE_URL,
- Expression().apply {
- language = "text/fhirpath"
- expression = "today()"
- },
- ),
- )
- },
- )
- },
+ fun `should return valid result and removes constraint for an answer value when minValue cqf-calculatedValue evaluates to empty`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ url = MIN_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ extension =
+ listOf(
+ Extension(
+ EXTENSION_CQF_CALCULATED_VALUE_URL,
+ Expression().apply {
+ language = "text/fhirpath"
+ expression = "yesterday()" // invalid FHIRPath expression
+ },
+ ),
+ )
+ },
+ )
+ },
+ )
+ }
+
+ val twoDaysAgo =
+ Date.from(
+ LocalDate.now().minusDays(2).atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(),
)
- }
+ val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType(twoDaysAgo) }
- val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType() }
+ val validationResult =
+ MinValueValidator.validate(
+ questionnaireItem,
+ answer,
+ InstrumentationRegistry.getInstrumentation().context,
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
- val validationResult =
- MinValueValidator.validate(
- questionnaireItem,
- answer,
- InstrumentationRegistry.getInstrumentation().context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
- }
+ assertThat(validationResult.isValid).isTrue()
+ assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
+ }
- assertThat(validationResult.isValid).isTrue()
- assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
- }
+ @Test
+ fun `should return valid result and removes constraint for an answer with an empty value`() =
+ runTest {
+ val questionnaireItem =
+ Questionnaire.QuestionnaireItemComponent().apply {
+ addExtension(
+ Extension().apply {
+ url = MIN_VALUE_EXTENSION_URL
+ this.setValue(
+ DateType().apply {
+ extension =
+ listOf(
+ Extension(
+ EXTENSION_CQF_CALCULATED_VALUE_URL,
+ Expression().apply {
+ language = "text/fhirpath"
+ expression = "today()"
+ },
+ ),
+ )
+ },
+ )
+ },
+ )
+ }
+
+ val answer = QuestionnaireResponseItemAnswerComponent().apply { value = DateType() }
+
+ val validationResult =
+ MinValueValidator.validate(
+ questionnaireItem,
+ answer,
+ InstrumentationRegistry.getInstrumentation().context,
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
+ }
+
+ assertThat(validationResult.isValid).isTrue()
+ assertThat(validationResult.errorMessage.isNullOrBlank()).isTrue()
+ }
}
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 d9f98eefd5..a0219d3f27 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
@@ -20,6 +20,7 @@ import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
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
@@ -43,7 +44,7 @@ class QuestionnaireResponseItemValidatorTest {
}
@Test
- fun `should return valid result`() {
+ fun `should return valid result`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -77,15 +78,15 @@ class QuestionnaireResponseItemValidatorTest {
questionnaireItem,
answers,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult).isEqualTo(Valid)
}
@Test
- fun `should validate individual answers and combine results`() {
+ fun `should validate individual answers and combine results`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "a-question"
@@ -121,8 +122,8 @@ class QuestionnaireResponseItemValidatorTest {
questionnaireItem,
answers,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult).isInstanceOf(Invalid::class.java)
@@ -132,7 +133,7 @@ class QuestionnaireResponseItemValidatorTest {
}
@Test
- fun `should validate all answers`() {
+ fun `should validate all answers`() = runTest {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
type = Questionnaire.QuestionnaireItemType.INTEGER
@@ -145,8 +146,8 @@ class QuestionnaireResponseItemValidatorTest {
questionnaireItem,
answers,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(questionnaireItem, it)
}
assertThat(validationResult).isInstanceOf(Invalid::class.java)
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RegexValidatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RegexValidatorTest.kt
index 1613e9f0b3..6baf6f5b98 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RegexValidatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/RegexValidatorTest.kt
@@ -22,6 +22,7 @@ import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import java.net.URI
import java.text.SimpleDateFormat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
@@ -52,37 +53,37 @@ class RegexValidatorTest {
}
@Test
- fun boolean_notMatchingRegex_shouldReturnInvalidResult() {
+ fun boolean_notMatchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(regex = "true", value = BooleanType(false))
}
@Test
- fun boolean_matchingRegex_shouldReturnValidResult() {
+ fun boolean_matchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(regex = "true", value = BooleanType(true))
}
@Test
- fun decimal_notMatchingRegex_shouldReturnInvalidResult() {
+ fun decimal_notMatchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(regex = "[0-9]+\\.[0-9]+", value = DecimalType(31234))
}
@Test
- fun decimal_matchingRegex_shouldReturnValidResult() {
+ fun decimal_matchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(regex = "[0-9]+\\.[0-9]+", value = DecimalType(3.1415926535))
}
@Test
- fun int_notMatchingRegex_shouldReturnInvalidResult() {
+ fun int_notMatchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(regex = "[0-9]+", value = IntegerType(-1))
}
@Test
- fun int_matchingRegex_shouldReturnValidResult() {
+ fun int_matchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(regex = "[0-9]+", value = IntegerType(1234567890))
}
@Test
- fun dateType_notMatchingRegex_shouldReturnInvalidResult() {
+ fun dateType_notMatchingRegex_shouldReturnInvalidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerNotMatchingRegex(
regex = "[0-9]{2}-[0-9]{2}-[0-9]{2}",
@@ -91,7 +92,7 @@ class RegexValidatorTest {
}
@Test
- fun date_matchingRegex_shouldReturnValidResult() {
+ fun date_matchingRegex_shouldReturnValidResult() = runTest {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
checkAnswerMatchingRegex(
regex = "[0-9]{4}-[0-9]{2}-[0-9]{2}",
@@ -100,17 +101,17 @@ class RegexValidatorTest {
}
@Test
- fun time_matchingRegex_shouldReturnInvalidResult() {
+ fun time_matchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(regex = "[0-9]{2}:[0-9]{2}", value = TimeType("18:00:59"))
}
@Test
- fun time_notMatchingRegex_shouldReturnValidResult() {
+ fun time_notMatchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(regex = "[0-9]{2}:[0-9]{2}:[0-9]{2}", value = TimeType("18:00:59"))
}
@Test
- fun string_notMatchingRegex_shouldReturnInvalidResult() {
+ fun string_notMatchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(
regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]",
value = StringType("www.hl7.org"),
@@ -118,7 +119,7 @@ class RegexValidatorTest {
}
@Test
- fun string_matchingRegex_shouldReturnValidResult() {
+ fun string_matchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(
regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]",
value = StringType("https://www.hl7.org/"),
@@ -126,7 +127,7 @@ class RegexValidatorTest {
}
@Test
- fun uri_notMatchingRegex_shouldReturnInvalidResult() {
+ fun uri_notMatchingRegex_shouldReturnInvalidResult() = runTest {
checkAnswerNotMatchingRegex(
regex = "[a-z]+",
value = UriType(URI.create("https://www.hl7.org/")),
@@ -134,7 +135,7 @@ class RegexValidatorTest {
}
@Test
- fun uri_matchingRegex_shouldReturnValidResult() {
+ fun uri_matchingRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex(
regex = "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]",
value = UriType(URI.create("https://www.hl7.org/")),
@@ -142,12 +143,12 @@ class RegexValidatorTest {
}
@Test
- fun invalidRegex_shouldReturnValidResult() {
+ fun invalidRegex_shouldReturnValidResult() = runTest {
checkAnswerMatchingRegex("[.*", StringType("http://www.google.com"))
}
@Test
- fun nonPrimitive_notMatchingRegex_shouldReturnValidResult() {
+ fun nonPrimitive_notMatchingRegex_shouldReturnValidResult() = runTest {
val requirement =
Questionnaire.QuestionnaireItemComponent().apply {
addExtension(
@@ -161,8 +162,8 @@ class RegexValidatorTest {
QuestionnaireResponseItemAnswerComponent().apply { this.value = Quantity(1234567.89) }
val validationResult =
- RegexValidator.validate(requirement, response, context) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ RegexValidator.validate(requirement, response, context) {
+ TestExpressionValueEvaluator.evaluate(requirement, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -174,7 +175,7 @@ class RegexValidatorTest {
var context: Context = ApplicationProvider.getApplicationContext()
@JvmStatic
- fun checkAnswerMatchingRegex(regex: String, value: PrimitiveType<*>) {
+ suspend fun checkAnswerMatchingRegex(regex: String, value: PrimitiveType<*>) {
val testComponent = createRegexQuestionnaireTestItem(regex, value)
val validationResult =
@@ -182,8 +183,8 @@ class RegexValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isTrue()
@@ -191,7 +192,7 @@ class RegexValidatorTest {
}
@JvmStatic
- fun checkAnswerNotMatchingRegex(regex: String, value: PrimitiveType<*>) {
+ suspend fun checkAnswerNotMatchingRegex(regex: String, value: PrimitiveType<*>) {
val testComponent = createRegexQuestionnaireTestItem(regex, value)
val validationResult =
@@ -199,8 +200,8 @@ class RegexValidatorTest {
testComponent.requirement,
testComponent.answer,
context,
- ) { extension, expression ->
- CalculatedValueExpressionEvaluator.evaluate(extension.value, expression)
+ ) {
+ TestExpressionValueEvaluator.evaluate(testComponent.requirement, it)
}
assertThat(validationResult.isValid).isFalse()
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/CalculatedValueExpressionEvaluator.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
similarity index 83%
rename from datacapture/src/test/java/com/google/android/fhir/datacapture/validation/CalculatedValueExpressionEvaluator.kt
rename to datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
index b96020879b..5cb104fb75 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/CalculatedValueExpressionEvaluator.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
@@ -17,17 +17,18 @@
package com.google.android.fhir.datacapture.validation
import com.google.android.fhir.datacapture.fhirpath.evaluateToBase
+import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Type
-object CalculatedValueExpressionEvaluator {
+object TestExpressionValueEvaluator {
/**
* Doesn't handle expressions containing FHIRPath supplements
* https://build.fhir.org/ig/HL7/sdc/expressions.html#fhirpath-supplements
*/
- fun evaluate(type: Type, expression: Expression): Type? =
+ fun evaluate(base: Base, expression: Expression): Type? =
try {
- evaluateToBase(type, expression.expression).singleOrNull() as? Type
+ evaluateToBase(base, expression.expression).singleOrNull() as? Type
} catch (_: Exception) {
null
}