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

Hide items if all items they are depending on have also been hidden. #1790

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Expression
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.ValueSet
import timber.log.Timber
Expand All @@ -62,6 +63,14 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
/** The current questionnaire as questions are being answered. */
internal val questionnaire: Questionnaire
private lateinit var currentPageItems: List<QuestionnaireAdapterItem>
/**
* Map of [QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent] for
* [Questionnaire.QuestionnaireItemComponent]s that are disabled now. The answers will be used to
* pre-populate the [QuestionnaireResponse.QuestionnaireResponseItemComponent] once the item is
* enabled again.
*/
private val linkIdToAnswersMapForDisabledQuestionnaireResponseItem =
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
mutableMapOf<String, List<QuestionnaireResponseItemAnswerComponent>>()

init {
questionnaire =
Expand Down Expand Up @@ -567,13 +576,33 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat
questionnaireResponseItem: QuestionnaireResponse.QuestionnaireResponseItemComponent,
): List<QuestionnaireAdapterItem> {
// Disabled/hidden questions should not get QuestionnaireItemViewItem instances
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
if (questionnaireItem.isHidden) return emptyList()
val enabled =
EnablementEvaluator(questionnaireResponse)
.evaluate(questionnaireItem, questionnaireResponseItem)
if (!enabled || questionnaireItem.isHidden) {
if (!enabled) {
// If the item is not enabled, clear the answers that it may have from the previous enabled
// state. This will also prevent any questionnaire item that depends on the answer of this
// questionnaire item to be wrongly evaluated as well.
if (questionnaireResponseItem.hasAnswer()) {
linkIdToAnswersMapForDisabledQuestionnaireResponseItem[questionnaireResponseItem.linkId] =
questionnaireResponseItem.answer
questionnaireResponseItem.answer = listOf()
}
return emptyList()
}

if (linkIdToAnswersMapForDisabledQuestionnaireResponseItem.contains(
questionnaireResponseItem.linkId
)
) {
questionnaireResponseItem.answer =
linkIdToAnswersMapForDisabledQuestionnaireResponseItem[questionnaireResponseItem.linkId]
linkIdToAnswersMapForDisabledQuestionnaireResponseItem.remove(
questionnaireResponseItem.linkId
)
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
}

// Determine the validation result, which will be displayed on the item itself
val validationResult =
if (modifiedQuestionnaireResponseItemSet.contains(questionnaireResponseItem) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,96 @@ class QuestionnaireViewModelTest {
assertResourceEquals(viewModel.getQuestionnaireResponse(), questionnaireResponse)
}

@Test
aditya-07 marked this conversation as resolved.
Show resolved Hide resolved
fun `should disable all questions in a chain of dependent questions after top question is disabled`() =
runBlocking {
val questionnaire =
Questionnaire().apply {
id = "a-questionnaire"
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "question-1"
type = Questionnaire.QuestionnaireItemType.BOOLEAN
}
)
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "question-2"
type = Questionnaire.QuestionnaireItemType.BOOLEAN
addEnableWhen().apply {
answer = BooleanType(true)
question = "question-1"
operator = Questionnaire.QuestionnaireItemOperator.EQUAL
}
}
)
addItem(
Questionnaire.QuestionnaireItemComponent().apply {
linkId = "question-3"
type = Questionnaire.QuestionnaireItemType.BOOLEAN
addEnableWhen().apply {
answer = BooleanType(true)
question = "question-2"
operator = Questionnaire.QuestionnaireItemOperator.EQUAL
}
}
)
}

val questionnaireResponse =
QuestionnaireResponse().apply {
id = "a-questionnaire-response"
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "question-1"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = BooleanType(true)
}
)
}
)
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "question-2"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = BooleanType(true)
}
)
}
)
addItem(
QuestionnaireResponse.QuestionnaireResponseItemComponent().apply {
linkId = "question-3"
addAnswer(
QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = BooleanType(true)
}
)
}
)
}

state.set(EXTRA_QUESTIONNAIRE_JSON_STRING, printer.encodeResourceToString(questionnaire))
state.set(
EXTRA_QUESTIONNAIRE_RESPONSE_JSON_STRING,
printer.encodeResourceToString(questionnaireResponse)
)

val viewModel = QuestionnaireViewModel(context, state)
viewModel.runViewModelBlocking {
var items = viewModel.getQuestionnaireItemViewItemList().map { it.asQuestion() }
assertThat(items.map { it.questionnaireItem.linkId })
.containsExactly("question-1", "question-2", "question-3")

items.first { it.questionnaireItem.linkId == "question-1" }.clearAnswer()

items = viewModel.getQuestionnaireItemViewItemList().map { it.asQuestion() }
assertThat(items.map { it.questionnaireItem.linkId }).containsExactly("question-1")
}
}

// Test cases for state flow

@Test
Expand Down