diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
index 0acd26c93f..98f707f163 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
@@ -773,7 +773,7 @@ internal data class ChoiceColumn(val path: String, val label: String?, val forDi
* resources [Resource], identifiers [Identifier] or codes [Coding]
* @return list of answer options [Questionnaire.QuestionnaireItemAnswerOptionComponent]
*/
-internal fun QuestionnaireItemComponent.extractAnswerOptions(
+internal suspend fun QuestionnaireItemComponent.extractAnswerOptions(
dataList: List,
): List {
return when (this.type) {
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 97e74233e6..457e1df1aa 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
@@ -24,12 +24,17 @@ import com.google.android.fhir.datacapture.extensions.isFhirPath
import com.google.android.fhir.datacapture.extensions.isReferencedBy
import com.google.android.fhir.datacapture.extensions.isXFhirQuery
import com.google.android.fhir.datacapture.extensions.variableExpressions
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.fold
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import org.hl7.fhir.exceptions.FHIRException
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent
import org.hl7.fhir.r4.model.Expression
-import org.hl7.fhir.r4.model.ExpressionNode
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent
import org.hl7.fhir.r4.model.QuestionnaireResponse
@@ -348,7 +353,7 @@ internal class ExpressionEvaluator(
* Creates an x-fhir-query string for evaluation. For this, it evaluates both variables and
* fhir-paths in the expression.
*/
- internal fun createXFhirQueryFromExpression(
+ internal suspend fun createXFhirQueryFromExpression(
expression: Expression,
variablesMap: Map = emptyMap(),
): String {
@@ -357,6 +362,7 @@ internal class ExpressionEvaluator(
variablesMap
.filterKeys { expression.expression.contains("{{%$it}}") }
.map { Pair("{{%${it.key}}}", it.value?.primitiveValue() ?: "") }
+ .asFlow()
val fhirPathsEvaluatedPairs =
questionnaireLaunchContextMap
@@ -364,9 +370,9 @@ internal class ExpressionEvaluator(
.takeIf { !it.isNullOrEmpty() }
?.also { it.put(questionnaireFhirPathSupplement, questionnaire) }
?.let { evaluateXFhirEnhancement(expression, it) }
- ?: emptySequence()
+ ?: emptyFlow()
- return (variablesEvaluatedPairs + fhirPathsEvaluatedPairs).fold(expression.expression) {
+ return merge(variablesEvaluatedPairs, fhirPathsEvaluatedPairs).fold(expression.expression) {
acc: String,
pair: Pair,
->
@@ -383,21 +389,17 @@ internal class ExpressionEvaluator(
* Practitioner?active=true&{{Practitioner.name.family}}
* @param launchContextMap the launch context to evaluate the expression against
*/
- private fun evaluateXFhirEnhancement(
+ private suspend fun evaluateXFhirEnhancement(
expression: Expression,
launchContextMap: Map,
- ): Sequence> =
+ ): Flow> =
xFhirQueryEnhancementRegex
.findAll(expression.expression)
+ .asFlow()
.map { it.groupValues }
.map { (fhirPathWithParentheses, fhirPath) ->
- val expressionNode = extractExpressionNode(fhirPath)
val evaluatedResult =
- evaluateToString(
- expression = expressionNode,
- data = launchContextMap[extractResourceType(expressionNode)],
- contextMap = launchContextMap,
- )
+ evaluateToString(contextMap = launchContextMap, fhirPathString = fhirPath)
// If the result of evaluating the FHIRPath expressions is an invalid query, it returns
// null. As per the spec:
@@ -531,15 +533,5 @@ internal class ExpressionEvaluator(
}
}
-/**
- * Extract [ResourceType] string representation from constant or name property of given
- * [ExpressionNode].
- */
-private fun extractResourceType(expressionNode: ExpressionNode): String? {
- // TODO(omarismail94): See if FHIRPathEngine.check() can be used to distinguish invalid
- // expression vs an expression that is valid, but does not return one resource only.
- return expressionNode.constant?.primitiveValue()?.substring(1) ?: expressionNode.name?.lowercase()
-}
-
/** Pair of a [Questionnaire.QuestionnaireItemComponent] with its evaluated answers */
internal typealias ItemToAnswersPair = Pair>
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 1e86e4b8fd..66ad0e7a8f 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
@@ -18,6 +18,9 @@ package com.google.android.fhir.datacapture.fhirpath
import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext
import org.hl7.fhir.r4.model.Base
import org.hl7.fhir.r4.model.ExpressionNode
@@ -26,32 +29,52 @@ import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComp
import org.hl7.fhir.r4.model.Resource
import org.hl7.fhir.r4.utils.FHIRPathEngine
-private val fhirPathEngine: FHIRPathEngine =
+private val fhirPathEngine: FHIRPathEngine by lazy {
with(FhirContext.forCached(FhirVersionEnum.R4)) {
FHIRPathEngine(HapiWorkerContext(this, this.validationSupport)).apply {
hostServices = FHIRPathEngineHostServices
}
}
+}
+
+internal var fhirPathEngineDefaultDispatcher: CoroutineContext = Dispatchers.Default
/**
* Evaluates the expressions over list of resources [Resource] and joins to space separated string
*/
-internal fun evaluateToDisplay(expressions: List, data: Resource) =
- expressions.joinToString(" ") { fhirPathEngine.evaluateToString(data, it) }
+internal suspend fun evaluateToDisplay(expressions: List, data: Resource) =
+ withContext(fhirPathEngineDefaultDispatcher) {
+ expressions.joinToString(" ") { fhirPathEngine.evaluateToString(data, it) }
+ }
/** Evaluates the expression over resource [Resource] and returns string value */
-internal fun evaluateToString(
+internal suspend fun evaluateToString(
expression: ExpressionNode,
data: Resource?,
contextMap: Map,
) =
- fhirPathEngine.evaluateToString(
- /* appInfo = */ contextMap,
- /* focusResource = */ null,
- /* rootResource = */ null,
- /* base = */ data,
- /* node = */ expression,
+ withContext(fhirPathEngineDefaultDispatcher) {
+ fhirPathEngine.evaluateToString(
+ /* appInfo = */ contextMap,
+ /* focusResource = */ null,
+ /* rootResource = */ null,
+ /* base = */ data,
+ /* node = */ expression,
+ )
+ }
+
+/** Evaluates FhirPath expression string over a contextMap and returns string value */
+internal suspend fun evaluateToString(
+ contextMap: Map,
+ fhirPathString: String,
+): String {
+ val expressionNode = extractExpressionNode(fhirPathString)
+ return evaluateToString(
+ expression = expressionNode,
+ data = contextMap[extractResourceType(expressionNode)],
+ contextMap = contextMap,
)
+}
/**
* Evaluates the expression and returns the boolean result. The resources [QuestionnaireResponse]
@@ -60,20 +83,22 @@ internal fun evaluateToString(
*
* %resource = [QuestionnaireResponse], %context = [QuestionnaireResponseItemComponent]
*/
-internal fun evaluateToBoolean(
+internal suspend fun evaluateToBoolean(
questionnaireResponse: QuestionnaireResponse,
questionnaireResponseItemComponent: QuestionnaireResponseItemComponent,
expression: String,
contextMap: Map = mapOf(),
): Boolean {
- val expressionNode = fhirPathEngine.parse(expression)
- return fhirPathEngine.evaluateToBoolean(
- contextMap,
- questionnaireResponse,
- null,
- questionnaireResponseItemComponent,
- expressionNode,
- )
+ return withContext(fhirPathEngineDefaultDispatcher) {
+ val expressionNode = fhirPathEngine.parse(expression)
+ fhirPathEngine.evaluateToBoolean(
+ contextMap,
+ questionnaireResponse,
+ null,
+ questionnaireResponseItemComponent,
+ expressionNode,
+ )
+ }
}
/**
@@ -84,31 +109,47 @@ internal fun evaluateToBoolean(
*
* %resource = [QuestionnaireResponse], %context = [QuestionnaireResponseItemComponent]
*/
-internal fun evaluateToBase(
+internal suspend fun evaluateToBase(
questionnaireResponse: QuestionnaireResponse?,
questionnaireResponseItem: QuestionnaireResponseItemComponent?,
expression: String,
contextMap: Map = mapOf(),
): List {
- return fhirPathEngine.evaluate(
- /* appContext = */ contextMap,
- /* focusResource = */ questionnaireResponse,
- /* rootResource = */ null,
- /* base = */ questionnaireResponseItem,
- /* path = */ expression,
- )
+ return withContext(fhirPathEngineDefaultDispatcher) {
+ fhirPathEngine.evaluate(
+ /* appContext = */ contextMap,
+ /* focusResource = */ questionnaireResponse,
+ /* rootResource = */ null,
+ /* base = */ questionnaireResponseItem,
+ /* path = */ expression,
+ )
+ }
}
/** Evaluates the given expression and returns list of [Base] */
-internal fun evaluateToBase(base: Base, expression: String): List {
- return fhirPathEngine.evaluate(
- /* base = */ base,
- /* path = */ expression,
- )
+internal suspend fun evaluateToBase(base: Base, expression: String): List {
+ return withContext(fhirPathEngineDefaultDispatcher) {
+ fhirPathEngine.evaluate(
+ /* base = */ base,
+ /* path = */ expression,
+ )
+ }
}
/** Evaluates the given list of [Base] elements and returns boolean result */
-internal fun convertToBoolean(items: List) = fhirPathEngine.convertToBoolean(items)
+internal suspend fun convertToBoolean(items: List) =
+ withContext(fhirPathEngineDefaultDispatcher) { fhirPathEngine.convertToBoolean(items) }
/** Parse the given expression into [ExpressionNode] */
-internal fun extractExpressionNode(fhirPath: String) = fhirPathEngine.parse(fhirPath)
+internal suspend fun extractExpressionNode(fhirPath: String) =
+ withContext(fhirPathEngineDefaultDispatcher) { fhirPathEngine.parse(fhirPath) }
+
+/**
+ * Extract [ResourceType] string representation from constant or name property of given
+ * [ExpressionNode].
+ */
+private fun extractResourceType(expressionNode: ExpressionNode): String? {
+ // TODO(omarismail94): See if FHIRPathEngine.check() can be used to distinguish invalid
+ // expression vs an expression that is valid, but does not return one resource only.
+ return expressionNode.constant?.primitiveValue()?.substring(1) ?: expressionNode.name?.lowercase()
+}
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt
index e644e4c6d6..40d248fb35 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/QuestionnaireViewModelTest.kt
@@ -56,6 +56,7 @@ import com.google.android.fhir.datacapture.extensions.createNestedQuestionnaireR
import com.google.android.fhir.datacapture.extensions.entryMode
import com.google.android.fhir.datacapture.extensions.logicalId
import com.google.android.fhir.datacapture.extensions.maxValue
+import com.google.android.fhir.datacapture.fhirpath.fhirPathEngineDefaultDispatcher
import com.google.android.fhir.datacapture.testing.DataCaptureTestApplication
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.validation.MAX_VALUE_EXTENSION_URL
@@ -157,6 +158,7 @@ class QuestionnaireViewModelTest {
"Few tests require a custom application class that implements DataCaptureConfig.Provider"
}
ReflectionHelpers.setStaticField(DataCapture::class.java, "configuration", null)
+ fhirPathEngineDefaultDispatcher = mainDispatcherRule.testDispatcher
}
// ==================================================================== //
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponentsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponentsTest.kt
index beaf16d400..699d1e26d9 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponentsTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponentsTest.kt
@@ -27,6 +27,7 @@ import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal
import java.util.Locale
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.Attachment
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.CodeType
@@ -2220,7 +2221,7 @@ class MoreQuestionnaireItemComponentsTest {
}
@Test
- fun `extractAnswerOptions should return answer options for coding`() {
+ fun `extractAnswerOptions should return answer options for coding`() = runTest {
val questionItem =
Questionnaire()
.addItem(
@@ -2253,7 +2254,7 @@ class MoreQuestionnaireItemComponentsTest {
}
@Test
- fun `extractAnswerOptions should return answer options for resources`() {
+ fun `extractAnswerOptions should return answer options for resources`() = runTest {
val questionItem =
Questionnaire()
.addItem(
@@ -2302,34 +2303,35 @@ class MoreQuestionnaireItemComponentsTest {
}
@Test
- fun `extractAnswerOptions should throw IllegalArgumentException when item type is not reference and data type is resource`() {
- val questionItem =
- Questionnaire()
- .addItem(
- Questionnaire.QuestionnaireItemComponent().apply {
- linkId = "full-name"
- type = Questionnaire.QuestionnaireItemType.CHOICE
- extension =
- listOf(
- Extension(EXTENSION_CHOICE_COLUMN_URL).apply {
- addExtension(Extension("path", StringType("name.given")))
- addExtension(Extension("label", StringType("GIVEN")))
- addExtension(Extension("forDisplay", BooleanType(true)))
- },
- )
- },
- )
-
- assertThrows(IllegalArgumentException::class.java) {
- questionItem.itemFirstRep.extractAnswerOptions(listOf(Patient()))
- }
- .run {
- assertThat(this.message)
- .isEqualTo(
- "$EXTENSION_CHOICE_COLUMN_URL not applicable for 'choice'. Only type reference is allowed with resource.",
+ fun `extractAnswerOptions should throw IllegalArgumentException when item type is not reference and data type is resource`() =
+ runTest {
+ val questionItem =
+ Questionnaire()
+ .addItem(
+ Questionnaire.QuestionnaireItemComponent().apply {
+ linkId = "full-name"
+ type = Questionnaire.QuestionnaireItemType.CHOICE
+ extension =
+ listOf(
+ Extension(EXTENSION_CHOICE_COLUMN_URL).apply {
+ addExtension(Extension("path", StringType("name.given")))
+ addExtension(Extension("label", StringType("GIVEN")))
+ addExtension(Extension("forDisplay", BooleanType(true)))
+ },
+ )
+ },
)
- }
- }
+
+ assertThrows(IllegalArgumentException::class.java) {
+ runBlocking { questionItem.itemFirstRep.extractAnswerOptions(listOf(Patient())) }
+ }
+ .run {
+ assertThat(this.message)
+ .isEqualTo(
+ "$EXTENSION_CHOICE_COLUMN_URL not applicable for 'choice'. Only type reference is allowed with resource.",
+ )
+ }
+ }
@Test
fun `sliderStepValue should return the integer value in the sliderStepValue extension`() {
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluatorTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluatorTest.kt
index 910cb0e36f..4f9f46d5b2 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluatorTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/ExpressionEvaluatorTest.kt
@@ -28,6 +28,7 @@ import java.util.Calendar
import java.util.Date
import java.util.UUID
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.Address
import org.hl7.fhir.r4.model.Bundle
import org.hl7.fhir.r4.model.DateType
@@ -893,7 +894,7 @@ class ExpressionEvaluatorTest {
}
@Test
- fun `createXFhirQueryFromExpression() should capture all FHIR paths`() {
+ fun `createXFhirQueryFromExpression() should capture all FHIR paths`() = runTest {
val expression =
Expression().apply {
this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
@@ -919,37 +920,38 @@ class ExpressionEvaluatorTest {
}
@Test
- fun `createXFhirQueryFromExpression() should evaluate to empty string for field that does not exist in resource`() {
- val practitioner =
- Practitioner().apply {
- id = UUID.randomUUID().toString()
- active = true
- addName(HumanName().apply { this.family = "John" })
- }
+ fun `createXFhirQueryFromExpression() should evaluate to empty string for field that does not exist in resource`() =
+ runTest {
+ val practitioner =
+ Practitioner().apply {
+ id = UUID.randomUUID().toString()
+ active = true
+ addName(HumanName().apply { this.family = "John" })
+ }
- val expression =
- Expression().apply {
- this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
- this.expression = "Practitioner?gender={{Practitioner.gender}}"
- }
+ val expression =
+ Expression().apply {
+ this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
+ this.expression = "Practitioner?gender={{Practitioner.gender}}"
+ }
- val expressionEvaluator =
- ExpressionEvaluator(
- Questionnaire(),
- QuestionnaireResponse(),
- questionnaireItemParentMap = emptyMap(),
- questionnaireLaunchContextMap = mapOf(practitioner.resourceType.name to practitioner),
- )
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ Questionnaire(),
+ QuestionnaireResponse(),
+ questionnaireItemParentMap = emptyMap(),
+ questionnaireLaunchContextMap = mapOf(practitioner.resourceType.name to practitioner),
+ )
- val expressionsToEvaluate =
- expressionEvaluator.createXFhirQueryFromExpression(
- expression,
- )
- assertThat(expressionsToEvaluate).isEqualTo("Practitioner?gender=")
- }
+ val expressionsToEvaluate =
+ expressionEvaluator.createXFhirQueryFromExpression(
+ expression,
+ )
+ assertThat(expressionsToEvaluate).isEqualTo("Practitioner?gender=")
+ }
@Test
- fun `createXFhirQueryFromExpression() should evaluate correct expression`() {
+ fun `createXFhirQueryFromExpression() should evaluate correct expression`() = runTest {
val practitioner =
Practitioner().apply {
id = UUID.randomUUID().toString()
@@ -981,38 +983,39 @@ class ExpressionEvaluatorTest {
}
@Test
- fun `createXFhirQueryFromExpression() should return empty string if the resource provided does not match the type in the expression`() {
- val practitioner =
- Practitioner().apply {
- id = UUID.randomUUID().toString()
- active = true
- gender = Enumerations.AdministrativeGender.MALE
- addName(HumanName().apply { this.family = "John" })
- }
+ fun `createXFhirQueryFromExpression() should return empty string if the resource provided does not match the type in the expression`() =
+ runTest {
+ val practitioner =
+ Practitioner().apply {
+ id = UUID.randomUUID().toString()
+ active = true
+ gender = Enumerations.AdministrativeGender.MALE
+ addName(HumanName().apply { this.family = "John" })
+ }
- val expression =
- Expression().apply {
- this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
- this.expression = "Practitioner?gender={{%patient.gender}}"
- }
+ val expression =
+ Expression().apply {
+ this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
+ this.expression = "Practitioner?gender={{%patient.gender}}"
+ }
- val expressionEvaluator =
- ExpressionEvaluator(
- Questionnaire(),
- QuestionnaireResponse(),
- questionnaireItemParentMap = emptyMap(),
- questionnaireLaunchContextMap = mapOf(practitioner.resourceType.name to practitioner),
- )
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ Questionnaire(),
+ QuestionnaireResponse(),
+ questionnaireItemParentMap = emptyMap(),
+ questionnaireLaunchContextMap = mapOf(practitioner.resourceType.name to practitioner),
+ )
- val expressionsToEvaluate =
- expressionEvaluator.createXFhirQueryFromExpression(
- expression,
- )
- assertThat(expressionsToEvaluate).isEqualTo("Practitioner?gender=")
- }
+ val expressionsToEvaluate =
+ expressionEvaluator.createXFhirQueryFromExpression(
+ expression,
+ )
+ assertThat(expressionsToEvaluate).isEqualTo("Practitioner?gender=")
+ }
@Test
- fun `createXFhirQueryFromExpression() should evaluate fhirPath with percent sign`() {
+ fun `createXFhirQueryFromExpression() should evaluate fhirPath with percent sign`() = runTest {
val patient =
Patient().apply {
id = UUID.randomUUID().toString()
@@ -1043,53 +1046,54 @@ class ExpressionEvaluatorTest {
}
@Test
- fun `createXFhirQueryFromExpression() should evaluate when multiple fhir paths are given`() {
- val patient =
- Patient().apply {
- id = UUID.randomUUID().toString()
- active = true
- gender = Enumerations.AdministrativeGender.MALE
- addName(HumanName().apply { this.family = "John" })
- }
+ fun `createXFhirQueryFromExpression() should evaluate when multiple fhir paths are given`() =
+ runTest {
+ val patient =
+ Patient().apply {
+ id = UUID.randomUUID().toString()
+ active = true
+ gender = Enumerations.AdministrativeGender.MALE
+ addName(HumanName().apply { this.family = "John" })
+ }
- val location =
- Location().apply {
- id = UUID.randomUUID().toString()
- status = Location.LocationStatus.ACTIVE
- mode = Location.LocationMode.INSTANCE
- address =
- Address().apply {
- use = Address.AddressUse.HOME
- type = Address.AddressType.PHYSICAL
- city = "NAIROBI"
- }
- }
+ val location =
+ Location().apply {
+ id = UUID.randomUUID().toString()
+ status = Location.LocationStatus.ACTIVE
+ mode = Location.LocationMode.INSTANCE
+ address =
+ Address().apply {
+ use = Address.AddressUse.HOME
+ type = Address.AddressType.PHYSICAL
+ city = "NAIROBI"
+ }
+ }
- val expression =
- Expression().apply {
- this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
- this.expression =
- "Patient?family={{%patient.name.family}}&address-city={{%location.address.city}}"
- }
+ val expression =
+ Expression().apply {
+ this.language = Expression.ExpressionLanguage.APPLICATION_XFHIRQUERY.toCode()
+ this.expression =
+ "Patient?family={{%patient.name.family}}&address-city={{%location.address.city}}"
+ }
- val expressionEvaluator =
- ExpressionEvaluator(
- Questionnaire(),
- QuestionnaireResponse(),
- questionnaireItemParentMap = emptyMap(),
- questionnaireLaunchContextMap =
- mapOf(
- patient.resourceType.name.lowercase() to patient,
- location.resourceType.name.lowercase() to location,
- ),
- )
+ val expressionEvaluator =
+ ExpressionEvaluator(
+ Questionnaire(),
+ QuestionnaireResponse(),
+ questionnaireItemParentMap = emptyMap(),
+ questionnaireLaunchContextMap =
+ mapOf(
+ patient.resourceType.name.lowercase() to patient,
+ location.resourceType.name.lowercase() to location,
+ ),
+ )
- val expressionsToEvaluate =
- expressionEvaluator.createXFhirQueryFromExpression(
- expression,
- )
- assertThat(expressionsToEvaluate).isEqualTo("Patient?family=John&address-city=NAIROBI")
- }
+ val expressionsToEvaluate =
+ expressionEvaluator.createXFhirQueryFromExpression(
+ expression,
+ )
+ assertThat(expressionsToEvaluate).isEqualTo("Patient?family=John&address-city=NAIROBI")
+ }
@Test
fun `createXFhirQueryFromExpression() should evaluate variables in answer expression when launch context is null`() {
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtilTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtilTest.kt
index dca7a71767..b40b485dc4 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtilTest.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/fhirpath/FhirPathUtilTest.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.
@@ -17,6 +17,7 @@
package com.google.android.fhir.datacapture.fhirpath
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.hl7.fhir.r4.model.HumanName
import org.hl7.fhir.r4.model.Patient
import org.junit.Test
@@ -27,18 +28,19 @@ import org.robolectric.RobolectricTestRunner
class FhirPathUtilTest {
@Test
- fun `evaluateToDisplay should return concatenated string for expressions evaluation on given resource`() {
- val expressions = listOf("name.given", "name.family")
- val resource =
- Patient().apply {
- addName(
- HumanName().apply {
- this.family = "Doe"
- this.addGiven("John")
- },
- )
- }
+ fun `evaluateToDisplay should return concatenated string for expressions evaluation on given resource`() =
+ runTest {
+ val expressions = listOf("name.given", "name.family")
+ val resource =
+ Patient().apply {
+ addName(
+ HumanName().apply {
+ this.family = "Doe"
+ this.addGiven("John")
+ },
+ )
+ }
- assertThat(evaluateToDisplay(expressions, resource)).isEqualTo("John Doe")
- }
+ assertThat(evaluateToDisplay(expressions, resource)).isEqualTo("John Doe")
+ }
}
diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
index 5cb104fb75..a6e329466c 100644
--- a/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
+++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/validation/TestExpressionValueEvaluator.kt
@@ -26,7 +26,7 @@ object TestExpressionValueEvaluator {
* Doesn't handle expressions containing FHIRPath supplements
* https://build.fhir.org/ig/HL7/sdc/expressions.html#fhirpath-supplements
*/
- fun evaluate(base: Base, expression: Expression): Type? =
+ suspend fun evaluate(base: Base, expression: Expression): Type? =
try {
evaluateToBase(base, expression.expression).singleOrNull() as? Type
} catch (_: Exception) {