Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate initial value from dropdown list for questionnaire items with type reference #2065

Merged
merged 27 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
cc22d43
set the initial value using the initial expression extension for refe…
omarismail94 Jan 30, 2023
3870224
Allow developers to add custom search parameter to the engine. (#1778)
aditya-07 Feb 6, 2023
dfe0f8d
Show submit button in review mode. (#1826)
aditya-07 Feb 6, 2023
551209d
Fix for removing old indexes when resource is updated (#1840)
aditya-07 Feb 6, 2023
87d2f5a
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 6, 2023
a2e4cde
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 6, 2023
f137a90
Merge remote-tracking branch 'upstream/master' into answerExpression
omarismail94 Feb 16, 2023
4d2c62a
add datacapture config in demo app
omarismail94 Feb 16, 2023
75bc566
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 26, 2023
4886ba2
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 27, 2023
2a32685
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jun 29, 2023
b6283f8
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 4, 2023
2f76ca3
Fix dropdown initial selection for answerOptions of type Reference
LZRS Jul 5, 2023
8700742
Support expression evaluating to ResourceType for items of type Refer…
LZRS Jul 5, 2023
08c0a0a
Merge branch 'master' into answerExpression
LZRS Jul 11, 2023
1639a7d
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 18, 2023
bdd7363
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Jul 24, 2023
6de72b7
Update autocompleteTextView to check answerId
LZRS Jul 24, 2023
4674086
Add AutoCompleteViewHolderFactory ui tests
LZRS Aug 2, 2023
7f39ec3
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Aug 2, 2023
f38e6f3
Fix failing tests for AutoCompleteViewHolderFactory
LZRS Aug 2, 2023
4fed0f7
Merge branch 'master' into answerExpression
LZRS Aug 10, 2023
f214488
Refactor MoreTypes.kt to remove duplications
LZRS Aug 14, 2023
d4ab2a9
Refactor evaluation of questionnaireItem initial value
LZRS Aug 14, 2023
6b576ec
Merge remote-tracking branch 'upstream/master' into answerExpression
LZRS Aug 14, 2023
84537e2
Fix AutoCompleteTextViewFactory espresso test failing in api 24
LZRS Aug 16, 2023
9d2d603
Merge branch 'master' into answerExpression
LZRS Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.android.fhir.datacapture.test.views
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
Expand All @@ -35,15 +36,19 @@ import com.google.android.fhir.datacapture.test.TestActivity
import com.google.android.fhir.datacapture.test.utilities.showDropDown
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.fhir.datacapture.views.factories.DropDownAnswerOption
import com.google.android.fhir.datacapture.views.factories.DropDownViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
import com.google.android.material.textfield.MaterialAutoCompleteTextView
import com.google.common.truth.Truth.assertThat
import org.hamcrest.Matchers.instanceOf
import org.hamcrest.Matchers.`is`
import org.hl7.fhir.r4.model.Attachment
import org.hl7.fhir.r4.model.Coding
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.Reference
import org.hl7.fhir.r4.model.StringType
import org.junit.Before
import org.junit.Rule
Expand Down Expand Up @@ -264,6 +269,53 @@ class DropDownViewHolderFactoryEspressoTest {
.isEqualTo(0)
}

@Test
fun shouldSetCorrectDropDownValueToAutoCompleteTextViewForDifferentAnswerOptionsWithSimilarDisplayString() {
val questionnaireItem =
Questionnaire.QuestionnaireItemComponent().apply {
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
id = "ref_1"
display = "Reference"
}
}
)
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Reference().apply {
id = "ref_2"
display = "Reference"
}
}
)
}

var answerHolder: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>? = null
val questionnaireViewItem =
QuestionnaireViewItem(
questionnaireItem,
responseValueStringOptions(),
validationResult = NotValidated,
answersChangedCallback = { _, _, answers, _ -> answerHolder = answers }
)

runOnUI { viewHolder.bind(questionnaireViewItem) }

onView(withId(R.id.auto_complete)).perform(showDropDown())
onData(`is`(instanceOf(DropDownAnswerOption::class.java)))
.atPosition(2)
.inRoot(isPlatformPopup())
.perform(click())

assertThat(viewHolder.itemView.findViewById<TextView>(R.id.auto_complete).text.toString())
.isEqualTo("Reference")
assertThat((answerHolder!!.single().value as Reference).display).isEqualTo("Reference")
assertThat((answerHolder!!.single().value as Reference).id).isEqualTo("ref_2")
}

/** Method to run code snippet on UI/main thread */
private fun runOnUI(action: () -> Unit) {
activityScenarioRule.scenario.onActivity { action() }
Expand Down Expand Up @@ -295,7 +347,11 @@ class DropDownViewHolderFactoryEspressoTest {
responses.forEach { response ->
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = Coding().apply { display = response }
value =
Coding().apply {
code = response.replace(" ", "_")
display = response
}
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright 2023 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.views.factories

import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.RootMatchers
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.test.TestActivity
import com.google.android.fhir.datacapture.validation.NotValidated
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.material.chip.ChipGroup
import com.google.android.material.textfield.MaterialAutoCompleteTextView
import com.google.common.truth.Truth.assertThat
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class AutoCompleteViewHolderFactoryEspressoTest {
@Rule
@JvmField
var activityScenarioRule: ActivityScenarioRule<TestActivity> =
ActivityScenarioRule(TestActivity::class.java)

private lateinit var parent: FrameLayout
private lateinit var viewHolder: QuestionnaireItemViewHolder

@Before
fun setup() {
activityScenarioRule.scenario.onActivity { activity -> parent = FrameLayout(activity) }
viewHolder = AutoCompleteViewHolderFactory.create(parent)
setTestLayout(viewHolder.itemView)
}

@Test
fun shouldReturnFilteredDropDownMenuItems() {
val questionnaireViewItem =
QuestionnaireViewItem(
answerOptions(false, "Coding 1", "Coding 2", "Coding 3", "Coding 4", "Coding 5"),
responseOptions(),
validationResult = NotValidated,
answersChangedCallback = { _, _, _, _ -> },
)
runOnUI { viewHolder.bind(questionnaireViewItem) }

onView(ViewMatchers.withId(R.id.autoCompleteTextView)).perform(ViewActions.typeText("Coding 1"))
assertThat(
viewHolder.itemView
.findViewById<MaterialAutoCompleteTextView>(R.id.autoCompleteTextView)
.adapter.count
)
.isEqualTo(1)
}

@Test
fun shouldAddDropDownValueSelectedForMultipleAnswersAutoCompleteTextView() {
var answerHolder: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent>? = null
val questionnaireViewItem =
QuestionnaireViewItem(
answerOptions(true, "Coding 1", "Coding 2", "Coding 3", "Coding 4", "Coding 5"),
responseOptions("Coding 1", "Coding 5"),
validationResult = NotValidated,
answersChangedCallback = { _, _, answers, _ -> answerHolder = answers },
)
runOnUI { viewHolder.bind(questionnaireViewItem) }

onView(ViewMatchers.withId(R.id.autoCompleteTextView)).perform(ViewActions.typeText("Coding 3"))
onView(ViewMatchers.withText("Coding 3"))
.inRoot(RootMatchers.isPlatformPopup())
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
.perform(ViewActions.click())
assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.autoCompleteTextView).text.toString()
)
.isEmpty()
assertThat(answerHolder!!.map { it.valueCoding.display })
.containsExactly("Coding 1", "Coding 5", "Coding 3")
}

@Test
fun shouldSetCorrectNumberOfChipsForSelectedAnswers() {
val questionnaireViewItem =
QuestionnaireViewItem(
answerOptions(true, "Coding 1", "Coding 2", "Coding 3", "Coding 4", "Coding 5"),
responseOptions("Coding 1", "Coding 5"),
validationResult = NotValidated,
answersChangedCallback = { _, _, _, _ -> },
)
runOnUI { viewHolder.bind(questionnaireViewItem) }

assertThat(viewHolder.itemView.findViewById<ChipGroup>(R.id.chipContainer).childCount)
.isEqualTo(2)
}

/** Method to run code snippet on UI/main thread */
private fun runOnUI(action: () -> Unit) {
activityScenarioRule.scenario.onActivity { action() }
}

/** Method to set content view for test activity */
private fun setTestLayout(view: View) {
activityScenarioRule.scenario.onActivity { activity -> activity.setContentView(view) }
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
}

private fun answerOptions(repeats: Boolean, vararg options: String) =
Questionnaire.QuestionnaireItemComponent().apply {
this.repeats = repeats
options.forEach { option ->
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value =
Coding().apply {
code = option.replace(" ", "_")
display = option
}
}
)
}
}

private fun responseOptions(vararg options: String) =
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
options.forEach { option ->
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value =
Coding().apply {
code = option.replace(" ", "_")
display = option
}
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ import org.hl7.fhir.r4.model.CodeType
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.DateTimeType
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.PrimitiveType
import org.hl7.fhir.r4.model.Quantity
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.Reference
import org.hl7.fhir.r4.model.StringType
import org.hl7.fhir.r4.model.TimeType
import org.hl7.fhir.r4.model.Type
import org.hl7.fhir.r4.model.UriType

Expand All @@ -59,54 +56,45 @@ fun Type.asStringValue(): String {
* [Questionnaire.QuestionnaireItemAnswerOptionComponent].
*/
fun Type.displayString(context: Context): String =
when (this) {
is Attachment -> this.url ?: context.getString(R.string.not_answered)
getDisplayString(this, context) ?: context.getString(R.string.not_answered)

/** Returns value as string depending on the [Type] of element. */
fun Type.getValueAsString(context: Context): String =
getValueString(this) ?: context.getString(R.string.not_answered)

/*
* Returns the unique identifier of a [Type]. Used to differentiate between item answer options that
* may have similar display strings
*/
fun Type.identifierString(context: Context): String =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly worried we're not dealing with identify of codes carefully enough. can we make this function much more rigorous please?

for example, comparing two coding just by code but not by system doesn't meet the brief of returning "unique identifier".

id ?: (this as? Coding)?.code ?: (this as? Reference)?.reference ?: displayString(context)

private fun getDisplayString(type: Type, context: Context): String? =
when (type) {
is Coding -> type.displayElement.getLocalizedText() ?: type.display ?: type.code
is StringType -> type.getLocalizedText() ?: type.asStringValue()
is DateType -> type.localDate?.format()
is DateTimeType -> "${type.localDate.format()} ${type.localTime.toLocalizedString(context)}"
is Reference -> type.display ?: type.reference
is Attachment -> type.url
is BooleanType -> {
when (this.value) {
when (type.value) {
true -> context.getString(R.string.yes)
false -> context.getString(R.string.no)
null -> context.getString(R.string.not_answered)
null -> null
}
}
is Coding -> {
val display = this.displayElement.getLocalizedText() ?: this.display
if (display.isNullOrEmpty()) {
this.code ?: context.getString(R.string.not_answered)
} else display
}
is DateType -> this.localDate?.format() ?: context.getString(R.string.not_answered)
is DateTimeType -> "${this.localDate.format()} ${this.localTime.toLocalizedString(context)}"
is DecimalType,
is IntegerType -> (this as PrimitiveType<*>).valueAsString
?: context.getString(R.string.not_answered)
is Quantity -> this.value.toString()
is Reference -> {
val display = this.display
if (display.isNullOrEmpty()) {
this.reference ?: context.getString(R.string.not_answered)
} else display
}
is StringType -> this.getLocalizedText()
?: this.valueAsString ?: context.getString(R.string.not_answered)
is TimeType,
is UriType -> (this as PrimitiveType<*>).valueAsString
?: context.getString(R.string.not_answered)
else -> context.getString(R.string.not_answered)
is Quantity -> type.value.toString()
else -> (type as? PrimitiveType<*>)?.valueAsString
}

/** Returns value as string depending on the [Type] of element. */
fun Type.getValueAsString(context: Context): String =
when (this) {
is DateType -> this.valueAsString ?: context.getString(R.string.not_answered)
is DateTimeType -> this.valueAsString ?: context.getString(R.string.not_answered)
is Quantity -> this.value.toString()
is StringType -> this.getLocalizedText()
?: this.valueAsString ?: context.getString(R.string.not_answered)
is DecimalType,
is IntegerType,
is TimeType -> (this as PrimitiveType<*>).valueAsString
?: context.getString(R.string.not_answered)
else -> context.getString(R.string.not_answered)
private fun getValueString(type: Type): String? =
when (type) {
is DateType,
is DateTimeType,
is StringType -> type.asStringValue()
is Quantity -> type.value.toString()
else -> (type as? PrimitiveType<*>)?.valueAsString
}

/** Converts StringType to toUriType. */
Expand Down
Loading