Skip to content

Commit b241ad9

Browse files
committed
Prohibit datePicker input when minValue/maxValue conflict
Instead of throwing an IllegalArgumentException
1 parent 0881d34 commit b241ad9

File tree

4 files changed

+48
-38
lines changed

4 files changed

+48
-38
lines changed

datacapture/src/androidTest/java/com/google/android/fhir/datacapture/test/QuestionnaireUiEspressoTest.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.ui.semantics.SemanticsProperties
2323
import androidx.compose.ui.test.SemanticsMatcher
2424
import androidx.compose.ui.test.assert
2525
import androidx.compose.ui.test.assertIsDisplayed
26+
import androidx.compose.ui.test.assertIsNotEnabled
2627
import androidx.compose.ui.test.assertTextEquals
2728
import androidx.compose.ui.test.junit4.createAndroidComposeRule
2829
import androidx.compose.ui.test.hasAnyAncestor
@@ -78,7 +79,6 @@ import org.hl7.fhir.r4.model.DateTimeType
7879
import org.hl7.fhir.r4.model.DateType
7980
import org.hl7.fhir.r4.model.Questionnaire
8081
import org.hl7.fhir.r4.model.QuestionnaireResponse
81-
import org.junit.Assert
8282
import org.junit.Before
8383
import org.junit.Rule
8484
import org.junit.Test
@@ -466,7 +466,7 @@ class QuestionnaireUiEspressoTest {
466466
}
467467

468468
@Test
469-
fun datePicker_shouldThrowException_whenMinValueRangeIsGreaterThanMaxValueRange() {
469+
fun datePicker_shouldProhibitInputWithErrorMessage_whenMinValueRangeIsGreaterThanMaxValueRange() {
470470
val questionnaire =
471471
Questionnaire().apply {
472472
id = "a-questionnaire"
@@ -490,18 +490,18 @@ class QuestionnaireUiEspressoTest {
490490
}
491491

492492
buildFragmentFromQuestionnaire(questionnaire)
493-
val exception =
494-
Assert.assertThrows(IllegalArgumentException::class.java) {
495-
composeTestRule
496-
.onNodeWithContentDescription(context.getString(R.string.select_date))
497-
.performClick()
498-
composeTestRule
499-
.onNode(hasText("OK") and hasAnyAncestor(isDialog()))
500-
.assertIsDisplayed()
501-
.performClick()
502-
composeTestRule.waitForIdle() // Synchronize
503-
}
504-
assertThat(exception.message).isEqualTo("minValue cannot be greater than maxValue")
493+
composeTestRule
494+
.onNodeWithTag(DATE_TEXT_INPUT_FIELD)
495+
.assert(
496+
SemanticsMatcher.expectValue(
497+
SemanticsProperties.Error,
498+
"minValue cannot be greater than maxValue",
499+
),
500+
)
501+
composeTestRule.onNodeWithTag(DATE_TEXT_INPUT_FIELD).assertIsNotEnabled()
502+
composeTestRule
503+
.onNodeWithContentDescription(context.getString(R.string.select_date))
504+
.assertIsNotEnabled()
505505
}
506506

507507
@Test

datacapture/src/androidTest/java/com/google/android/fhir/datacapture/test/views/DatePickerViewHolderFactoryTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.compose.ui.test.assertIsNotEnabled
2727
import androidx.compose.ui.test.assertTextContains
2828
import androidx.compose.ui.test.assertTextEquals
2929
import androidx.compose.ui.test.junit4.createEmptyComposeRule
30+
import androidx.compose.ui.test.onNodeWithContentDescription
3031
import androidx.compose.ui.test.onNodeWithTag
3132
import androidx.compose.ui.test.onNodeWithText
3233
import androidx.compose.ui.test.performSemanticsAction
@@ -549,6 +550,9 @@ class DatePickerViewHolderFactoryTest {
549550
)
550551

551552
composeTestRule.onNodeWithTag(DATE_TEXT_INPUT_FIELD).assertIsNotEnabled()
553+
composeTestRule
554+
.onNodeWithContentDescription(viewHolder.itemView.context.getString(R.string.select_date))
555+
.assertIsNotEnabled()
552556
}
553557

554558
@Test

datacapture/src/main/java/com/google/android/fhir/datacapture/views/compose/DatePickerItem.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ internal fun DatePickerItem(
6363
isError: Boolean,
6464
enabled: Boolean,
6565
dateInputFormat: DateInputFormat,
66-
selectableDates: () -> SelectableDates,
66+
selectableDates: SelectableDates?,
6767
parseStringToLocalDate: (String, DateFormatPattern) -> LocalDate?,
6868
onDateInputEntry: (DateInput) -> Unit,
6969
) {
@@ -136,7 +136,7 @@ internal fun DatePickerItem(
136136
visualTransformation = DateVisualTransformation(dateInputFormat),
137137
)
138138

139-
if (showDatePickerModal) {
139+
if (selectableDates != null && showDatePickerModal) {
140140
DatePickerModal(
141141
initialSelectedDateMillis,
142142
selectableDates,
@@ -159,12 +159,12 @@ internal fun DatePickerItem(
159159
@Composable
160160
internal fun DatePickerModal(
161161
initialSelectedDateMillis: Long?,
162-
selectableDates: () -> SelectableDates,
162+
selectableDates: SelectableDates,
163163
onDateSelected: (Long?) -> Unit,
164164
onDismiss: () -> Unit,
165165
) {
166166
val datePickerState =
167-
rememberDatePickerState(initialSelectedDateMillis, selectableDates = selectableDates())
167+
rememberDatePickerState(initialSelectedDateMillis, selectableDates = selectableDates)
168168
val datePickerSelectedDateMillis =
169169
remember(initialSelectedDateMillis) { initialSelectedDateMillis }
170170
val confirmEnabled by remember { derivedStateOf { datePickerState.selectedDateMillis != null } }

datacapture/src/main/java/com/google/android/fhir/datacapture/views/factories/DatePickerViewHolderFactory.kt

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -106,28 +106,34 @@ internal object DatePickerViewHolderFactory : QuestionnaireItemComposeViewHolder
106106
?.let { DateInput(it, questionnaireItemAnswerLocalDate) }
107107
?: DateInput(display = draftAnswer ?: "", null)
108108
}
109+
110+
val selectableDatesResult =
111+
remember(questionnaireViewItem) { getSelectableDates(questionnaireViewItem) }
112+
113+
val selectableDates = remember(selectableDatesResult) { selectableDatesResult.getOrNull() }
114+
115+
val prohibitInput = remember(selectableDatesResult) { selectableDatesResult.isFailure }
116+
109117
val validationMessage =
110-
remember(draftAnswer) {
111-
// If the draft answer is set, this means the user has yet to type a parseable answer,
112-
// so we display an error.
113-
if (!draftAnswer.isNullOrEmpty()) {
114-
getValidationErrorMessage(
115-
context,
116-
questionnaireViewItem,
117-
Invalid(
118-
listOf(invalidDateErrorText(context, canonicalizedDatePattern)),
119-
),
120-
)
118+
remember(draftAnswer, selectableDatesResult) {
119+
if (selectableDatesResult.isFailure) {
120+
selectableDatesResult.exceptionOrNull()?.localizedMessage
121121
} else {
122+
// If the draft answer is set, this means the user has yet to type a parseable answer,
123+
// so we display an error.
122124
getValidationErrorMessage(
123125
context,
124126
questionnaireViewItem,
125-
questionnaireViewItem.validationResult,
127+
if (!draftAnswer.isNullOrEmpty()) {
128+
Invalid(
129+
listOf(invalidDateErrorText(context, canonicalizedDatePattern)),
130+
)
131+
} else {
132+
questionnaireViewItem.validationResult
133+
},
126134
)
127135
}
128136
}
129-
val selectableDates =
130-
remember(questionnaireViewItem) { { getSelectableDates(questionnaireViewItem) } }
131137

132138
Column(
133139
modifier =
@@ -149,7 +155,7 @@ internal object DatePickerViewHolderFactory : QuestionnaireItemComposeViewHolder
149155
helperText = validationMessage.takeIf { !it.isNullOrBlank() }
150156
?: getRequiredOrOptionalText(questionnaireViewItem, context),
151157
isError = !validationMessage.isNullOrBlank(),
152-
enabled = !questionnaireViewItem.questionnaireItem.readOnly,
158+
enabled = !(questionnaireViewItem.questionnaireItem.readOnly || prohibitInput),
153159
parseStringToLocalDate = { str, pattern -> getLocalDate(str, pattern) },
154160
onDateInputEntry = {
155161
val (display, date) = it
@@ -173,15 +179,15 @@ internal object DatePickerViewHolderFactory : QuestionnaireItemComposeViewHolder
173179

174180
private fun getSelectableDates(
175181
questionnaireViewItem: QuestionnaireViewItem,
176-
): SelectableDates {
182+
): Result<SelectableDates> {
177183
val min = (questionnaireViewItem.minAnswerValue as? DateType)?.value?.time
178184
val max = (questionnaireViewItem.maxAnswerValue as? DateType)?.value?.time
179185

180-
if (min != null && max != null && min > max) {
181-
throw IllegalArgumentException("minValue cannot be greater than maxValue")
186+
return if (min != null && max != null && min > max) {
187+
Result.failure(IllegalArgumentException("minValue cannot be greater than maxValue"))
188+
} else {
189+
Result.success(selectableDates(min, max))
182190
}
183-
184-
return selectableDates(min, max)
185191
}
186192

187193
/** Set the answer in the [QuestionnaireResponse]. */

0 commit comments

Comments
 (0)