1717package com.google.android.fhir.datacapture
1818
1919import android.widget.FrameLayout
20- import androidx.core.os.bundleOf
21- import androidx.fragment.app.add
2220import androidx.fragment.app.commitNow
2321import androidx.test.espresso.Espresso.onView
2422import androidx.test.espresso.action.ViewActions
2523import androidx.test.espresso.action.ViewActions.typeText
2624import androidx.test.espresso.assertion.ViewAssertions
25+ import androidx.test.espresso.matcher.RootMatchers
2726import androidx.test.espresso.matcher.ViewMatchers
2827import androidx.test.espresso.matcher.ViewMatchers.withId
2928import androidx.test.ext.junit.rules.ActivityScenarioRule
3029import androidx.test.ext.junit.runners.AndroidJUnit4
31- import com.google.android.fhir.datacapture.TestQuestionnaireFragment.Companion.QUESTIONNAIRE_FILE_PATH_KEY
30+ import androidx.test.platform.app.InstrumentationRegistry
31+ import ca.uhn.fhir.context.FhirContext
32+ import ca.uhn.fhir.context.FhirVersionEnum
33+ import ca.uhn.fhir.parser.IParser
3234import com.google.android.fhir.datacapture.test.R
3335import com.google.android.fhir.datacapture.utilities.clickIcon
3436import com.google.android.fhir.datacapture.utilities.clickOnText
37+ import com.google.android.fhir.datacapture.validation.Invalid
38+ import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator
39+ import com.google.android.fhir.datacapture.validation.Valid
40+ import com.google.android.fhir.datacapture.views.factories.localDate
3541import com.google.android.fhir.datacapture.views.factories.localDateTime
3642import com.google.android.material.textfield.TextInputLayout
3743import com.google.common.truth.Truth.assertThat
44+ import java.time.LocalDate
3845import java.time.LocalDateTime
46+ import java.util.Calendar
47+ import java.util.Date
48+ import org.hamcrest.CoreMatchers
49+ import org.hl7.fhir.r4.model.DateTimeType
50+ import org.hl7.fhir.r4.model.DateType
51+ import org.hl7.fhir.r4.model.Questionnaire
3952import org.hl7.fhir.r4.model.QuestionnaireResponse
53+ import org.junit.Assert
4054import org.junit.Before
4155import org.junit.Rule
4256import org.junit.Test
@@ -51,6 +65,8 @@ class QuestionnaireUiEspressoTest {
5165 ActivityScenarioRule (TestActivity ::class .java)
5266
5367 private lateinit var parent: FrameLayout
68+ private val parser: IParser = FhirContext .forCached(FhirVersionEnum .R4 ).newJsonParser()
69+ private val context = InstrumentationRegistry .getInstrumentation().context
5470
5571 @Before
5672 fun setup () {
@@ -59,7 +75,7 @@ class QuestionnaireUiEspressoTest {
5975
6076 @Test
6177 fun shouldDisplayReviewButtonWhenNoMorePagesToDisplay () {
62- buildFragmentFromQuestionnaire(" /paginated_questionnaire_with_dependent_answer.json" )
78+ buildFragmentFromQuestionnaire(" /paginated_questionnaire_with_dependent_answer.json" , true )
6379
6480 onView(withId(R .id.review_mode_button))
6581 .check(
@@ -148,22 +164,243 @@ class QuestionnaireUiEspressoTest {
148164 assertThat(answer.localDateTime).isEqualTo(LocalDateTime .of(2005 , 1 , 5 , 6 , 10 ))
149165 }
150166
151- private fun buildFragmentFromQuestionnaire (fileName : String ) {
152- val bundle = bundleOf(QUESTIONNAIRE_FILE_PATH_KEY to fileName)
167+ @Test
168+ fun datePicker_shouldShowErrorForWrongDate () {
169+ buildFragmentFromQuestionnaire(" /component_date_picker.json" )
170+
171+ // Add month and day. No need to add slashes as they are added automatically
172+ onView(withId(R .id.text_input_edit_text))
173+ .perform(ViewActions .click())
174+ .perform(ViewActions .typeTextIntoFocusedView(" 0105" ))
175+
176+ onView(withId(R .id.text_input_layout)).check { view, _ ->
177+ val actualError = (view as TextInputLayout ).error
178+ assertThat(actualError).isEqualTo(" Date format needs to be MM/dd/yyyy (e.g. 01/31/2023)" )
179+ }
180+ }
181+
182+ @Test
183+ fun datePicker_shouldSaveInQuestionnaireResponseWhenCorrectDateEntered () {
184+ buildFragmentFromQuestionnaire(" /component_date_picker.json" )
185+
186+ onView(withId(R .id.text_input_edit_text))
187+ .perform(ViewActions .click())
188+ .perform(ViewActions .typeTextIntoFocusedView(" 01052005" ))
189+
190+ onView(withId(R .id.text_input_layout)).check { view, _ ->
191+ val actualError = (view as TextInputLayout ).error
192+ assertThat(actualError).isEqualTo(null )
193+ }
194+
195+ val answer = getQuestionnaireResponse().item.first().answer.first().valueDateType
196+ assertThat(answer.localDate).isEqualTo(LocalDate .of(2005 , 1 , 5 ))
197+ }
198+
199+ @Test
200+ fun datePicker_shouldSetDateInput_withinRange () {
201+ val questionnaire =
202+ Questionnaire ().apply {
203+ id = " a-questionnaire"
204+ addItem(
205+ Questionnaire .QuestionnaireItemComponent ().apply {
206+ type = Questionnaire .QuestionnaireItemType .DATE
207+ linkId = " link-1"
208+ addExtension().apply {
209+ url = " http://hl7.org/fhir/StructureDefinition/minValue"
210+ val minDate = DateType (Date ()).apply { add(Calendar .YEAR , - 1 ) }
211+ setValue(minDate)
212+ }
213+ addExtension().apply {
214+ url = " http://hl7.org/fhir/StructureDefinition/maxValue"
215+ val maxDate = DateType (Date ()).apply { add(Calendar .YEAR , 4 ) }
216+ setValue(maxDate)
217+ }
218+ }
219+ )
220+ }
221+
222+ buildFragmentFromQuestionnaire(questionnaire)
223+ onView(withId(R .id.text_input_layout)).perform(clickIcon(true ))
224+ onView(CoreMatchers .allOf(ViewMatchers .withText(" OK" )))
225+ .inRoot(RootMatchers .isDialog())
226+ .check(ViewAssertions .matches(ViewMatchers .isDisplayed()))
227+ .perform(ViewActions .click())
228+
229+ val today = DateTimeType .today().valueAsString
230+ val answer = getQuestionnaireResponse().item.first().answer.first().valueDateType.valueAsString
231+
232+ assertThat(answer).isEqualTo(today)
233+ val validationResult =
234+ QuestionnaireResponseValidator .validateQuestionnaireResponse(
235+ questionnaire,
236+ getQuestionnaireResponse(),
237+ context
238+ )
239+
240+ assertThat(validationResult[" link-1" ]?.first()).isEqualTo(Valid )
241+ }
242+
243+ @Test
244+ fun datePicker_shouldNotSetDateInput_outsideMaxRange () {
245+ val maxDate = DateType (Date ()).apply { add(Calendar .YEAR , - 2 ) }
246+ val questionnaire =
247+ Questionnaire ().apply {
248+ id = " a-questionnaire"
249+ addItem(
250+ Questionnaire .QuestionnaireItemComponent ().apply {
251+ type = Questionnaire .QuestionnaireItemType .DATE
252+ linkId = " link-1"
253+ addExtension().apply {
254+ url = " http://hl7.org/fhir/StructureDefinition/minValue"
255+ val minDate = DateType (Date ()).apply { add(Calendar .YEAR , - 4 ) }
256+ setValue(minDate)
257+ }
258+ addExtension().apply {
259+ url = " http://hl7.org/fhir/StructureDefinition/maxValue"
260+ setValue(maxDate)
261+ }
262+ }
263+ )
264+ }
265+
266+ buildFragmentFromQuestionnaire(questionnaire)
267+ onView(withId(R .id.text_input_layout)).perform(clickIcon(true ))
268+ onView(CoreMatchers .allOf(ViewMatchers .withText(" OK" )))
269+ .inRoot(RootMatchers .isDialog())
270+ .check(ViewAssertions .matches(ViewMatchers .isDisplayed()))
271+ .perform(ViewActions .click())
272+
273+ val maxDateAllowed = maxDate.valueAsString
274+ val validationResult =
275+ QuestionnaireResponseValidator .validateQuestionnaireResponse(
276+ questionnaire,
277+ getQuestionnaireResponse(),
278+ context
279+ )
280+
281+ assertThat((validationResult[" link-1" ]?.first() as Invalid ).getSingleStringValidationMessage())
282+ .isEqualTo(" Maximum value allowed is:$maxDateAllowed " )
283+ }
284+
285+ @Test
286+ fun datePicker_shouldNotSetDateInput_outsideMinRange () {
287+ val minDate = DateType (Date ()).apply { add(Calendar .YEAR , 1 ) }
288+ val questionnaire =
289+ Questionnaire ().apply {
290+ id = " a-questionnaire"
291+ addItem(
292+ Questionnaire .QuestionnaireItemComponent ().apply {
293+ type = Questionnaire .QuestionnaireItemType .DATE
294+ linkId = " link-1"
295+ addExtension().apply {
296+ url = " http://hl7.org/fhir/StructureDefinition/minValue"
297+ setValue(minDate)
298+ }
299+ addExtension().apply {
300+ url = " http://hl7.org/fhir/StructureDefinition/maxValue"
301+ val maxDate = DateType (Date ()).apply { add(Calendar .YEAR , 2 ) }
302+ setValue(maxDate)
303+ }
304+ }
305+ )
306+ }
307+
308+ buildFragmentFromQuestionnaire(questionnaire)
309+ onView(withId(R .id.text_input_layout)).perform(clickIcon(true ))
310+ onView(CoreMatchers .allOf(ViewMatchers .withText(" OK" )))
311+ .inRoot(RootMatchers .isDialog())
312+ .check(ViewAssertions .matches(ViewMatchers .isDisplayed()))
313+ .perform(ViewActions .click())
314+
315+ val minDateAllowed = minDate.valueAsString
316+ val validationResult =
317+ QuestionnaireResponseValidator .validateQuestionnaireResponse(
318+ questionnaire,
319+ getQuestionnaireResponse(),
320+ context
321+ )
322+
323+ assertThat((validationResult[" link-1" ]?.first() as Invalid ).getSingleStringValidationMessage())
324+ .isEqualTo(" Minimum value allowed is:$minDateAllowed " )
325+ }
326+
327+ @Test
328+ fun datePicker_shouldThrowException_whenMinValueRangeIsGreaterThanMaxValueRange () {
329+ val questionnaire =
330+ Questionnaire ().apply {
331+ id = " a-questionnaire"
332+ addItem(
333+ Questionnaire .QuestionnaireItemComponent ().apply {
334+ type = Questionnaire .QuestionnaireItemType .DATE
335+ linkId = " link-1"
336+ addExtension().apply {
337+ url = " http://hl7.org/fhir/StructureDefinition/minValue"
338+ val minDate = DateType (Date ()).apply { add(Calendar .YEAR , 1 ) }
339+
340+ setValue(minDate)
341+ }
342+ addExtension().apply {
343+ url = " http://hl7.org/fhir/StructureDefinition/maxValue"
344+ val maxDate = DateType (Date ()).apply { add(Calendar .YEAR , - 1 ) }
345+ setValue(maxDate)
346+ }
347+ }
348+ )
349+ }
350+
351+ buildFragmentFromQuestionnaire(questionnaire)
352+ val exception =
353+ Assert .assertThrows(IllegalArgumentException ::class .java) {
354+ onView(withId(com.google.android.fhir.datacapture.R .id.text_input_layout))
355+ .perform(clickIcon(true ))
356+ onView(CoreMatchers .allOf(ViewMatchers .withText(" OK" )))
357+ .inRoot(RootMatchers .isDialog())
358+ .check(ViewAssertions .matches(ViewMatchers .isDisplayed()))
359+ .perform(ViewActions .click())
360+ }
361+ assertThat(exception.message).isEqualTo(" minValue cannot be greater than maxValue" )
362+ }
363+
364+ private fun buildFragmentFromQuestionnaire (fileName : String , isReviewMode : Boolean = false) {
365+ val questionnaireJsonString = readFileFromAssets(fileName)
366+ val questionnaireFragment =
367+ QuestionnaireFragment .builder()
368+ .setQuestionnaire(questionnaireJsonString)
369+ .showReviewPageBeforeSubmit(isReviewMode)
370+ .build()
153371 activityScenarioRule.scenario.onActivity { activity ->
154372 activity.supportFragmentManager.commitNow {
155373 setReorderingAllowed(true )
156- add< TestQuestionnaireFragment > (R .id.container_holder, args = bundle )
374+ add(R .id.container_holder, questionnaireFragment )
157375 }
158376 }
159377 }
378+
379+ private fun buildFragmentFromQuestionnaire (
380+ questionnaire : Questionnaire ,
381+ isReviewMode : Boolean = false
382+ ) {
383+ val questionnaireFragment =
384+ QuestionnaireFragment .builder()
385+ .setQuestionnaire(parser.encodeResourceToString(questionnaire))
386+ .showReviewPageBeforeSubmit(isReviewMode)
387+ .build()
388+ activityScenarioRule.scenario.onActivity { activity ->
389+ activity.supportFragmentManager.commitNow {
390+ setReorderingAllowed(true )
391+ add(R .id.container_holder, questionnaireFragment)
392+ }
393+ }
394+ }
395+
396+ private fun readFileFromAssets (filename : String ) =
397+ javaClass.getResourceAsStream(filename)!! .bufferedReader().use { it.readText() }
160398 private fun getQuestionnaireResponse (): QuestionnaireResponse {
161399 var testQuestionnaireFragment: QuestionnaireFragment ? = null
162400 activityScenarioRule.scenario.onActivity { activity ->
163401 testQuestionnaireFragment =
164- activity.supportFragmentManager
165- .findFragmentById(R .id.container_holder)
166- ?.childFragmentManager?.findFragmentById(R .id.container) as QuestionnaireFragment
402+ activity.supportFragmentManager.findFragmentById(R .id.container_holder)
403+ as QuestionnaireFragment
167404 }
168405 return testQuestionnaireFragment!! .getQuestionnaireResponse()
169406 }
0 commit comments