Skip to content

Commit 409b45f

Browse files
santosh-pingleSantosh Pingleomarismail94
authored
Refactor the code for the DatePickerView (#1880)
* Refactor the code for the date picker view. * clean up. * clean up. * refactor to resemble DateTimePicer * Add tests * parse propah * clean up UiEspresso * fix tests * make function name more accurate * address comments * spotless apply duh * forgot to change names to match new names * add test case based on comment ' --------- Co-authored-by: Santosh Pingle <[email protected]> Co-authored-by: omarismail <[email protected]>
1 parent 2363c11 commit 409b45f

File tree

10 files changed

+389
-610
lines changed

10 files changed

+389
-610
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"resourceType": "Questionnaire",
3+
"item": [
4+
{
5+
"linkId": "1",
6+
"text": "Enter a date",
7+
"type": "date",
8+
"extension": [
9+
{
10+
"url": "http://hl7.org/fhir/StructureDefinition/entryFormat",
11+
"valueString": "yyyy-mm-dd"
12+
}
13+
],
14+
"item": [
15+
{
16+
"extension": [
17+
{
18+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
19+
"valueCodeableConcept": {
20+
"coding": [
21+
{
22+
"system": "http://hl7.org/fhir/questionnaire-display-category",
23+
"code": "instructions"
24+
}
25+
]
26+
}
27+
}
28+
],
29+
"linkId": "1-most-recent",
30+
"text": "Use keyboard entry or date picker",
31+
"type": "display"
32+
}
33+
]
34+
}
35+
]
36+
}

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

Lines changed: 247 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,40 @@
1717
package com.google.android.fhir.datacapture
1818

1919
import android.widget.FrameLayout
20-
import androidx.core.os.bundleOf
21-
import androidx.fragment.app.add
2220
import androidx.fragment.app.commitNow
2321
import androidx.test.espresso.Espresso.onView
2422
import androidx.test.espresso.action.ViewActions
2523
import androidx.test.espresso.action.ViewActions.typeText
2624
import androidx.test.espresso.assertion.ViewAssertions
25+
import androidx.test.espresso.matcher.RootMatchers
2726
import androidx.test.espresso.matcher.ViewMatchers
2827
import androidx.test.espresso.matcher.ViewMatchers.withId
2928
import androidx.test.ext.junit.rules.ActivityScenarioRule
3029
import 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
3234
import com.google.android.fhir.datacapture.test.R
3335
import com.google.android.fhir.datacapture.utilities.clickIcon
3436
import 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
3541
import com.google.android.fhir.datacapture.views.factories.localDateTime
3642
import com.google.android.material.textfield.TextInputLayout
3743
import com.google.common.truth.Truth.assertThat
44+
import java.time.LocalDate
3845
import 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
3952
import org.hl7.fhir.r4.model.QuestionnaireResponse
53+
import org.junit.Assert
4054
import org.junit.Before
4155
import org.junit.Rule
4256
import 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

Comments
 (0)