Skip to content

Commit

Permalink
Fix catalog application crashes when time is set using time picker… (#…
Browse files Browse the repository at this point in the history
…1200)

* [1194] Catalog application crashes when time is set using time picker widget

* [1194] Catalog application crashes when time is set using time picker widget

* [1194] App crash when time is selected using time picker

* testcases updated

* spotless check applied

* review point updated

* tese cases updated to run on device with API 24 and 27

* Animation Disabled and Handle Dialog Display in Test case
  • Loading branch information
PallaviGanorkar authored and ktarasenko committed Apr 12, 2022
1 parent a225157 commit 7bc3bc3
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 18 deletions.
3 changes: 3 additions & 0 deletions datacapture/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ android {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
configureJacocoTestOptions()

testOptions { animationsDisabled = true }
}

configurations { all { exclude(module = "xpp3") } }
Expand Down Expand Up @@ -111,4 +113,5 @@ dependencies {
testImplementation(Dependencies.mockitoKotlin)
testImplementation(Dependencies.robolectric)
testImplementation(Dependencies.truth)
androidTestImplementation(Dependencies.Espresso.espressoCore)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.datacapture.utilities

import android.view.View
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers
import com.google.android.fhir.datacapture.R
import com.google.android.material.internal.CheckableImageButton
import com.google.android.material.textfield.TextInputLayout

/** Clicks end or start icon in TextInputLayout widget. */
fun clickIcon(isEndIcon: Boolean): ViewAction {
return object : ViewAction {
override fun getConstraints(): org.hamcrest.Matcher<View> {
return ViewMatchers.isAssignableFrom(TextInputLayout::class.java)
}

override fun getDescription(): String {
return "Icon Click"
}

override fun perform(uiController: UiController?, view: View?) {
val item = view as TextInputLayout
val iconView: CheckableImageButton =
item.findViewById(if (isEndIcon) R.id.text_input_end_icon else R.id.text_input_start_icon)
iconView.performClick()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.datacapture.views

import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.TestActivity
import com.google.android.fhir.datacapture.utilities.clickIcon
import com.google.common.truth.Truth.assertThat
import org.hamcrest.CoreMatchers.allOf
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class QuestionnaireItemDateTimePickerViewHolderFactoryEspressoTest {

@Rule
@JvmField
var activityScenarioRule: ActivityScenarioRule<TestActivity> =
ActivityScenarioRule<TestActivity>(TestActivity::class.java)

private lateinit var parent: FrameLayout
private lateinit var viewHolder: QuestionnaireItemViewHolder
@Before
fun setup() {
activityScenarioRule.getScenario().onActivity { activity -> parent = FrameLayout(activity) }
viewHolder = QuestionnaireItemDateTimePickerViewHolderFactory.create(parent)
setTestLayout(viewHolder.itemView)
}

@Test
fun shouldSetFirstDateInputThenTimeInput() {
val questionnaireItemView =
QuestionnaireItemViewItem(
Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" },
QuestionnaireResponse.QuestionnaireResponseItemComponent()
) {}

runOnUI { viewHolder.bind(questionnaireItemView) }

assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.date_input_edit_text).text.toString()
)
.isEmpty()
assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.time_input_edit_text).text.toString()
)
.isEmpty()

onView(withId(R.id.date_input_layout)).perform(clickIcon(true))
onView(allOf(withText("OK")))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(ViewActions.click())
onView(withId(R.id.time_input_layout)).perform(clickIcon(true))
onView(allOf(withText("OK")))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(ViewActions.click())

assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.date_input_edit_text).text.toString()
)
.isNotEmpty()
assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.time_input_edit_text).text.toString()
)
.isNotEmpty()
}

@Test
fun shouldSetFirstTimeInputThenDateInput() {
val questionnaireItemView =
QuestionnaireItemViewItem(
Questionnaire.QuestionnaireItemComponent().apply { text = "Question?" },
QuestionnaireResponse.QuestionnaireResponseItemComponent()
) {}

runOnUI { viewHolder.bind(questionnaireItemView) }

assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.date_input_edit_text).text.toString()
)
.isEmpty()
assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.time_input_edit_text).text.toString()
)
.isEmpty()

onView(withId(R.id.time_input_layout)).perform(clickIcon(true))
onView(allOf(withText("OK")))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(ViewActions.click())
onView(withId(R.id.date_input_layout)).perform(clickIcon(true))
onView(allOf(withText("OK")))
.inRoot(isDialog())
.check(matches(isDisplayed()))
.perform(ViewActions.click())

assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.date_input_edit_text).text.toString()
)
.isNotEmpty()
assertThat(
viewHolder.itemView.findViewById<TextView>(R.id.time_input_edit_text).text.toString()
)
.isNotEmpty()
}

/** Method to run code snippet on UI/main thread */
private fun runOnUI(action: () -> Unit) {
activityScenarioRule.getScenario().onActivity { activity -> action() }
}

/** Method to set content view for test activity */
private fun setTestLayout(view: View) {
activityScenarioRule.getScenario().onActivity { activity -> activity.setContentView(view) }
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
private lateinit var timeInputLayout: TextInputLayout
private lateinit var timeInputEditText: TextInputEditText
override lateinit var questionnaireItemViewItem: QuestionnaireItemViewItem
private var localDate: LocalDate? = null
private var localTime: LocalTime? = null

override fun init(itemView: View) {
prefixTextView = itemView.findViewById(R.id.prefix_text_view)
Expand All @@ -72,21 +74,33 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
val year = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_YEAR)
val month = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_MONTH)
val dayOfMonth = result.getInt(DatePickerFragment.RESULT_BUNDLE_KEY_DAY_OF_MONTH)
val localDateTime =
LocalDateTime.of(
year,
// Month values are 1-12 in java.time but 0-11 in
// DatePickerDialog.
month + 1,
dayOfMonth,
0,
0,
0
)
localDate = LocalDate.of(year, month, dayOfMonth)
dateInputEditText.setText(localDate?.format(LOCAL_DATE_FORMATTER) ?: "")
var localDateTime: LocalDateTime? = null
if (localTime != null) {
localDateTime =
LocalDateTime.of(
year,
// Month values are 1-12 in java.time but 0-11 in
// DatePickerDialog.
month + 1,
dayOfMonth,
localTime!!.hour,
localTime!!.minute,
localTime!!.second
)
} else {
questionnaireItemViewItem.singleAnswerOrNull?.valueDateTimeType?.let {
localDateTime =
LocalDateTime.of(year, month + 1, dayOfMonth, it.hour, it.minute, it.second)
}
localDateTime?.let {
updateDateTimeInput(it)
updateDateTimeAnswer(it)
}
}
// Clear focus so that the user can refocus to open the dialog
dateInputEditText.clearFocus()
updateDateTimeInput(localDateTime)
updateDateTimeAnswer(localDateTime)
}

val selectedDate =
Expand All @@ -112,11 +126,28 @@ internal object QuestionnaireItemDateTimePickerViewHolderFactory :
) { _, result ->
val hour = result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_HOUR)
val minute = result.getInt(TimePickerFragment.RESULT_BUNDLE_KEY_MINUTE)
val localDate = questionnaireItemViewItem.singleAnswerOrNull!!.valueDateTimeType
val localDateTime =
LocalDateTime.of(localDate.year, localDate.month + 1, localDate.day, hour, minute, 0)
updateDateTimeInput(localDateTime)
updateDateTimeAnswer(localDateTime)
localTime = LocalTime.of(hour, minute, 0)
timeInputEditText.setText(localTime?.format(LOCAL_TIME_FORMATTER) ?: "")
var localDateTime: LocalDateTime? = null
if (localDate != null) {
localDateTime =
LocalDateTime.of(
localDate!!.year,
localDate!!.month + 1,
localDate!!.dayOfMonth,
hour,
minute,
0
)
} else {
questionnaireItemViewItem.singleAnswerOrNull?.valueDateTimeType?.let {
localDateTime = LocalDateTime.of(it.year, it.month + 1, it.day, hour, minute, 0)
}
}
localDateTime?.let {
updateDateTimeInput(it)
updateDateTimeAnswer(it)
}
}

val selectedTime =
Expand Down

0 comments on commit 7bc3bc3

Please sign in to comment.