Skip to content

Commit b63fa79

Browse files
committed
Prohibit datePicker input when minValue/maxValue conflict
Instead of throwing an IllegalArgumentException
1 parent 5488bfd commit b63fa79

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.hasAnyAncestor
2829
import androidx.compose.ui.test.hasText
@@ -79,7 +80,6 @@ import org.hl7.fhir.r4.model.DateTimeType
7980
import org.hl7.fhir.r4.model.DateType
8081
import org.hl7.fhir.r4.model.Questionnaire
8182
import org.hl7.fhir.r4.model.QuestionnaireResponse
82-
import org.junit.Assert
8383
import org.junit.Before
8484
import org.junit.Rule
8585
import org.junit.Test
@@ -467,7 +467,7 @@ class QuestionnaireUiEspressoTest {
467467
}
468468

469469
@Test
470-
fun datePicker_shouldThrowException_whenMinValueRangeIsGreaterThanMaxValueRange() {
470+
fun datePicker_shouldProhibitInputWithErrorMessage_whenMinValueRangeIsGreaterThanMaxValueRange() {
471471
val questionnaire =
472472
Questionnaire().apply {
473473
id = "a-questionnaire"
@@ -491,18 +491,18 @@ class QuestionnaireUiEspressoTest {
491491
}
492492

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

508508
@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)