From 7925e2afc515bed677702b73b9d84faabad4b15a Mon Sep 17 00:00:00 2001 From: santosh-pingle <86107848+santosh-pingle@users.noreply.github.com> Date: Tue, 12 Apr 2022 14:29:13 +0530 Subject: [PATCH] OptionExclusive extension support. (#1284) * OptionExclusive extension support. * Address review comments. --- ...xGroupViewHolderFactoryInstrumentedTest.kt | 80 +++++++++++++++++++ .../fhir/datacapture/MoreAnswerOptions.kt | 16 ++++ ...naireItemCheckBoxGroupViewHolderFactory.kt | 36 ++++++++- .../fhir/datacapture/MoreAnswerOptionsTest.kt | 23 ++++++ 4 files changed, 153 insertions(+), 2 deletions(-) diff --git a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactoryInstrumentedTest.kt b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactoryInstrumentedTest.kt index 308c862862..f4fb91a93c 100644 --- a/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactoryInstrumentedTest.kt +++ b/datacapture/src/androidTest/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactoryInstrumentedTest.kt @@ -29,10 +29,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.android.fhir.datacapture.ChoiceOrientationTypes import com.google.android.fhir.datacapture.EXTENSION_CHOICE_ORIENTATION_URL +import com.google.android.fhir.datacapture.EXTENSION_OPTION_EXCLUSIVE_URL import com.google.android.fhir.datacapture.R import com.google.common.truth.Truth.assertThat +import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.CodeType 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.junit.Test @@ -254,6 +257,83 @@ class QuestionnaireItemCheckBoxGroupViewHolderFactoryInstrumentedTest { assertThat(answer[0].valueCoding.display).isEqualTo("Coding 1") } + @Test + @UiThreadTest + fun optionExclusiveAnswerOption_click_deselectsOtherAnswerOptions() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + repeats = true + addAnswerOption( + Questionnaire.QuestionnaireItemAnswerOptionComponent().apply { + value = + Coding().apply { + code = "code-1" + display = "display-1" + } + extension = listOf(Extension(EXTENSION_OPTION_EXCLUSIVE_URL, BooleanType(true))) + } + ) + addAnswerOption( + Questionnaire.QuestionnaireItemAnswerOptionComponent().apply { + value = + Coding().apply { + code = "code-2" + display = "display-2" + } + } + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + viewHolder.bind(questionnaireItemViewItem) + val checkBoxGroup = viewHolder.itemView.findViewById(R.id.checkbox_group) + (checkBoxGroup.getChildAt(2) as CheckBox).performClick() + (checkBoxGroup.getChildAt(1) as CheckBox).performClick() + val answer = questionnaireItemViewItem.questionnaireResponseItem.answer + + assertThat(answer.single().valueCoding.display).isEqualTo("display-1") + } + + @Test + @UiThreadTest + fun answerOption_click_deselectsOptionExclusiveAnswerOption() { + val questionnaireItemViewItem = + QuestionnaireItemViewItem( + Questionnaire.QuestionnaireItemComponent().apply { + repeats = true + addAnswerOption( + Questionnaire.QuestionnaireItemAnswerOptionComponent().apply { + value = + Coding().apply { + code = "code-1" + display = "display-1" + } + extension = listOf(Extension(EXTENSION_OPTION_EXCLUSIVE_URL, BooleanType(true))) + } + ) + addAnswerOption( + Questionnaire.QuestionnaireItemAnswerOptionComponent().apply { + value = + Coding().apply { + code = "code-2" + display = "display-2" + } + } + ) + }, + QuestionnaireResponse.QuestionnaireResponseItemComponent() + ) {} + + viewHolder.bind(questionnaireItemViewItem) + val checkBoxGroup = viewHolder.itemView.findViewById(R.id.checkbox_group) + (checkBoxGroup.getChildAt(1) as CheckBox).performClick() + (checkBoxGroup.getChildAt(2) as CheckBox).performClick() + val answer = questionnaireItemViewItem.questionnaireResponseItem.answer + + assertThat(answer.single().valueCoding.display).isEqualTo("display-2") + } + @Test @UiThreadTest fun click_shouldRemoveQuestionnaireResponseItemAnswer() { diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt index aa535a61f8..f8e20b19ae 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/MoreAnswerOptions.kt @@ -17,8 +17,12 @@ package com.google.android.fhir.datacapture import com.google.android.fhir.getLocalizedText +import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Questionnaire +internal const val EXTENSION_OPTION_EXCLUSIVE_URL = + "http://hl7.org/fhir/StructureDefinition/questionnaire-optionExclusive" + val Questionnaire.QuestionnaireItemAnswerOptionComponent.displayString: String get() { if (!hasValueCoding()) { @@ -31,3 +35,15 @@ val Questionnaire.QuestionnaireItemAnswerOptionComponent.displayString: String display } } + +/** Indicates that if this answerOption is selected, no other possible answers may be selected. */ +internal val Questionnaire.QuestionnaireItemAnswerOptionComponent.optionExclusive: Boolean + get() { + val extension = + this.extension.singleOrNull { it.url == EXTENSION_OPTION_EXCLUSIVE_URL } ?: return false + val value = extension.value + if (value is BooleanType) { + return value.booleanValue() + } + return false + } diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt index e7a8f2b09e..0743504625 100644 --- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt +++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/QuestionnaireItemCheckBoxGroupViewHolderFactory.kt @@ -28,6 +28,7 @@ import com.google.android.fhir.datacapture.R import com.google.android.fhir.datacapture.choiceOrientation import com.google.android.fhir.datacapture.localizedPrefixSpanned import com.google.android.fhir.datacapture.localizedTextSpanned +import com.google.android.fhir.datacapture.optionExclusive import com.google.android.fhir.datacapture.subtitleText import com.google.android.fhir.datacapture.validation.ValidationResult import com.google.android.fhir.datacapture.validation.getSingleStringValidationMessage @@ -124,18 +125,49 @@ internal object QuestionnaireItemCheckBoxGroupViewHolderFactory : ) setOnClickListener { when (isChecked) { - true -> + true -> { questionnaireItemViewItem.addAnswer( QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { value = answerOption.value } ) - false -> + if (answerOption.optionExclusive) { + // if this answer option has optionExclusive extension, then deselect other + // answer options. + val optionExclusiveIndex = checkboxGroup.indexOfChild(it) - 1 + for (i in 0 until questionnaireItemViewItem.answerOption.size) { + if (optionExclusiveIndex == i) { + continue + } + (checkboxGroup.getChildAt(i + 1) as CheckBox).isChecked = false + questionnaireItemViewItem.removeAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + value = questionnaireItemViewItem.answerOption[i].value + } + ) + } + } else { + // deselect optionExclusive answer option. + for (i in 0 until questionnaireItemViewItem.answerOption.size) { + if (!questionnaireItemViewItem.answerOption[i].optionExclusive) { + continue + } + (checkboxGroup.getChildAt(i + 1) as CheckBox).isChecked = false + questionnaireItemViewItem.removeAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { + value = questionnaireItemViewItem.answerOption[i].value + } + ) + } + } + } + false -> { questionnaireItemViewItem.removeAnswer( QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply { value = answerOption.value } ) + } } onAnswerChanged(checkboxGroup.context) diff --git a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt index b8ca5167d5..a4fd8199d5 100644 --- a/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt +++ b/datacapture/src/test/java/com/google/android/fhir/datacapture/MoreAnswerOptionsTest.kt @@ -20,6 +20,8 @@ import android.os.Build import com.google.common.truth.Truth.assertThat import java.util.Locale import kotlin.test.assertFailsWith +import kotlinx.coroutines.runBlocking +import org.hl7.fhir.r4.model.BooleanType import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Questionnaire @@ -102,4 +104,25 @@ class MoreAnswerOptionsTest { assertThat(answerOption.displayString).isEqualTo("Test Code") } + + @Test + fun optionExclusiveExtension_valueTrue_returnsTrue() = runBlocking { + val answerOptionTest = Coding("test", "option", "1") + val questionnaire = + Questionnaire().apply { + id = "a-questionnaire" + addItem( + Questionnaire.QuestionnaireItemComponent().apply { + answerOption = + listOf( + Questionnaire.QuestionnaireItemAnswerOptionComponent(answerOptionTest).apply { + extension = listOf(Extension(EXTENSION_OPTION_EXCLUSIVE_URL, BooleanType(true))) + }, + ) + } + ) + } + + assertThat(questionnaire.item.single().answerOption.single().optionExclusive).isTrue() + } }