Skip to content

Commit 9f0e8ad

Browse files
authored
Migrate EditTextViewHolderFactory to compose (#2871)
* Migrate EditTextViewHolderFactory to compose Affecting questionnaire item of types string, integer, text, decimal, phone * Add compose tests for item Header * Rename EditTextStringViewHolderDelegate to EditTextStringDelegateFactory
1 parent 16c5180 commit 9f0e8ad

File tree

38 files changed

+2337
-1297
lines changed

38 files changed

+2337
-1297
lines changed

catalog/src/main/assets/component_text_fields.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,59 @@
150150
]
151151
}
152152
]
153+
},
154+
{
155+
"linkId": "6",
156+
"type": "text",
157+
"item": [
158+
{
159+
"linkId": "6.1",
160+
"text": "Enter text (multiline)",
161+
"type": "display",
162+
"extension": [
163+
{
164+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
165+
"valueCodeableConcept": {
166+
"coding": [
167+
{
168+
"system": "http://hl7.org/fhir/questionnaire-item-control",
169+
"code": "flyover",
170+
"display": "Fly-over"
171+
}
172+
],
173+
"text": "Flyover"
174+
}
175+
}
176+
]
177+
}
178+
]
179+
},
180+
{
181+
"linkId": "7",
182+
"type": "string",
183+
"readOnly": true,
184+
"item": [
185+
{
186+
"linkId": "7.1",
187+
"text": "Enter a string (readonly)",
188+
"type": "display",
189+
"extension": [
190+
{
191+
"url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
192+
"valueCodeableConcept": {
193+
"coding": [
194+
{
195+
"system": "http://hl7.org/fhir/questionnaire-item-control",
196+
"code": "flyover",
197+
"display": "Fly-over"
198+
}
199+
],
200+
"text": "Flyover"
201+
}
202+
}
203+
]
204+
}
205+
]
153206
}
154207
]
155208
}

datacapture/src/androidTest/java/com/google/android/fhir/datacapture/contrib/views/PhoneNumberViewHolderFactoryInstrumentedTest.kt

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 Google LLC
2+
* Copyright 2023-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,15 @@ package com.google.android.fhir.datacapture.contrib.views
1919
import android.view.View
2020
import android.widget.FrameLayout
2121
import android.widget.TextView
22-
import androidx.test.annotation.UiThreadTest
22+
import androidx.compose.ui.test.IdlingResource
23+
import androidx.compose.ui.test.assertIsDisplayed
24+
import androidx.compose.ui.test.assertIsNotEnabled
25+
import androidx.compose.ui.test.assertTextEquals
26+
import androidx.compose.ui.test.junit4.createEmptyComposeRule
27+
import androidx.compose.ui.test.onNodeWithContentDescription
28+
import androidx.compose.ui.test.onNodeWithTag
29+
import androidx.compose.ui.test.onNodeWithText
30+
import androidx.compose.ui.test.performTextReplacement
2331
import androidx.test.ext.junit.rules.ActivityScenarioRule
2432
import androidx.test.ext.junit.runners.AndroidJUnit4
2533
import androidx.test.platform.app.InstrumentationRegistry
@@ -30,42 +38,54 @@ import com.google.android.fhir.datacapture.test.TestActivity
3038
import com.google.android.fhir.datacapture.validation.Invalid
3139
import com.google.android.fhir.datacapture.validation.NotValidated
3240
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
41+
import com.google.android.fhir.datacapture.views.compose.EDIT_TEXT_FIELD_TEST_TAG
3342
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolder
34-
import com.google.android.material.textfield.TextInputEditText
35-
import com.google.android.material.textfield.TextInputLayout
3643
import com.google.common.truth.Truth.assertThat
3744
import org.hl7.fhir.r4.model.IntegerType
3845
import org.hl7.fhir.r4.model.Questionnaire
3946
import org.hl7.fhir.r4.model.QuestionnaireResponse
4047
import org.hl7.fhir.r4.model.StringType
48+
import org.junit.After
4149
import org.junit.Before
42-
import org.junit.Ignore
4350
import org.junit.Rule
4451
import org.junit.Test
4552
import org.junit.runner.RunWith
4653

4754
@RunWith(AndroidJUnit4::class)
4855
class PhoneNumberViewHolderFactoryInstrumentedTest {
4956

50-
@Rule
51-
@JvmField
52-
var activityScenarioRule: ActivityScenarioRule<TestActivity> =
57+
@get:Rule
58+
val activityScenarioRule: ActivityScenarioRule<TestActivity> =
5359
ActivityScenarioRule(TestActivity::class.java)
5460

61+
@get:Rule val composeTestRule = createEmptyComposeRule()
62+
5563
private lateinit var parent: FrameLayout
5664
private lateinit var viewHolder: QuestionnaireItemViewHolder
57-
private lateinit var questionnaireEditAdapter: QuestionnaireEditAdapter
65+
66+
private var pendingTextChange = 0
67+
private val handlingTextIdlingResource =
68+
object : IdlingResource {
69+
override val isIdleNow: Boolean
70+
get() = pendingTextChange == 0
71+
}
5872

5973
@Before
6074
fun setUp() {
6175
activityScenarioRule.scenario.onActivity { activity -> parent = FrameLayout(activity) }
6276
viewHolder = PhoneNumberViewHolderFactory.create(parent)
6377
setTestLayout(viewHolder.itemView)
64-
questionnaireEditAdapter = QuestionnaireEditAdapter()
78+
composeTestRule.registerIdlingResource(handlingTextIdlingResource)
79+
}
80+
81+
@After
82+
fun tearDown() {
83+
composeTestRule.unregisterIdlingResource(handlingTextIdlingResource)
6584
}
6685

6786
@Test
6887
fun createViewHolder_phoneNumberViewHolderFactory_returnsViewHolder() {
88+
val questionnaireEditAdapter = QuestionnaireEditAdapter()
6989
val viewHolderFromAdapter =
7090
questionnaireEditAdapter.createViewHolder(
7191
parent,
@@ -75,16 +95,14 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
7595
)
7696
.viewType,
7797
) as QuestionnaireEditAdapter.ViewHolder.QuestionHolder
98+
7899
assertThat(
79-
viewHolderFromAdapter.holder.itemView
80-
.findViewById<TextInputEditText>(R.id.text_input_edit_text)
81-
.visibility,
100+
viewHolderFromAdapter.holder.itemView.visibility,
82101
)
83102
.isEqualTo(View.VISIBLE)
84103
}
85104

86105
@Test
87-
@UiThreadTest
88106
fun shouldSetTextViewText() {
89107
viewHolder.bind(
90108
QuestionnaireViewItem(
@@ -94,13 +112,14 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
94112
answersChangedCallback = { _, _, _, _ -> },
95113
),
96114
)
115+
// Synchronize
116+
composeTestRule.waitForIdle()
97117

98118
assertThat(viewHolder.itemView.findViewById<TextView>(R.id.question).text.toString())
99119
.isEqualTo("Question?")
100120
}
101121

102122
@Test
103-
@UiThreadTest
104123
fun shouldSetInputText() {
105124
viewHolder.bind(
106125
QuestionnaireViewItem(
@@ -116,17 +135,10 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
116135
),
117136
)
118137

119-
assertThat(
120-
viewHolder.itemView
121-
.findViewById<TextInputEditText>(R.id.text_input_edit_text)
122-
.text
123-
.toString(),
124-
)
125-
.isEqualTo("+12345678910")
138+
composeTestRule.onNodeWithTag(EDIT_TEXT_FIELD_TEST_TAG).assertTextEquals("+12345678910")
126139
}
127140

128141
@Test
129-
@UiThreadTest
130142
fun shouldSetInputTextToEmpty() {
131143
viewHolder.bind(
132144
QuestionnaireViewItem(
@@ -150,36 +162,33 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
150162
),
151163
)
152164

153-
assertThat(
154-
viewHolder.itemView
155-
.findViewById<TextInputEditText>(R.id.text_input_edit_text)
156-
.text
157-
.toString(),
158-
)
159-
.isEqualTo("")
165+
composeTestRule.onNodeWithTag(EDIT_TEXT_FIELD_TEST_TAG).assertTextEquals("")
160166
}
161167

162168
@Test
163-
@UiThreadTest
164-
@Ignore("https://github.com/google/android-fhir/issues/1494")
165169
fun shouldSetQuestionnaireResponseItemAnswer() {
170+
var answers: List<QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent> = emptyList()
166171
val questionnaireViewItem =
167172
QuestionnaireViewItem(
168173
Questionnaire.QuestionnaireItemComponent(),
169174
QuestionnaireResponse.QuestionnaireResponseItemComponent(),
170175
validationResult = NotValidated,
171-
answersChangedCallback = { _, _, _, _ -> },
176+
answersChangedCallback = { _, _, newAnswers, _ ->
177+
answers = newAnswers
178+
pendingTextChange -= if (pendingTextChange > 0) 1 else 0
179+
},
172180
)
173181
viewHolder.bind(questionnaireViewItem)
174-
viewHolder.itemView
175-
.findViewById<TextInputEditText>(R.id.text_input_edit_text)
176-
.setText("+12345678910")
177-
assertThat(questionnaireViewItem.answers.single().valueStringType.value)
178-
.isEqualTo("+12345678910")
182+
composeTestRule
183+
.onNodeWithTag(EDIT_TEXT_FIELD_TEST_TAG)
184+
.performTextReplacement("+12345678910")
185+
.also { pendingTextChange += 1 }
186+
187+
composeTestRule.waitForIdle()
188+
assertThat(answers.single().valueStringType.value).isEqualTo("+12345678910")
179189
}
180190

181191
@Test
182-
@UiThreadTest
183192
fun shouldSetQuestionnaireResponseItemAnswerToEmpty() {
184193
val questionnaireViewItem =
185194
QuestionnaireViewItem(
@@ -190,13 +199,12 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
190199
)
191200

192201
viewHolder.bind(questionnaireViewItem)
193-
viewHolder.itemView.findViewById<TextInputEditText>(R.id.text_input_edit_text).setText("")
202+
composeTestRule.onNodeWithTag(EDIT_TEXT_FIELD_TEST_TAG).performTextReplacement("")
194203

195204
assertThat(questionnaireViewItem.answers).isEmpty()
196205
}
197206

198207
@Test
199-
@UiThreadTest
200208
fun displayValidationResult_noError_shouldShowNoErrorMessage() {
201209
viewHolder.bind(
202210
QuestionnaireViewItem(
@@ -218,12 +226,10 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
218226
),
219227
)
220228

221-
assertThat(viewHolder.itemView.findViewById<TextInputLayout>(R.id.text_input_layout).error)
222-
.isNull()
229+
composeTestRule.onNodeWithContentDescription("Error").assertDoesNotExist()
223230
}
224231

225232
@Test
226-
@UiThreadTest
227233
fun displayValidationResult_error_shouldShowErrorMessage() {
228234
viewHolder.bind(
229235
QuestionnaireViewItem(
@@ -242,12 +248,14 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
242248
answersChangedCallback = { _, _, _, _ -> },
243249
),
244250
)
245-
assertThat(viewHolder.itemView.findViewById<TextInputLayout>(R.id.text_input_layout).error)
246-
.isEqualTo("The maximum number of characters that are permitted in the answer is: 10")
251+
252+
composeTestRule.onNodeWithContentDescription("Error").assertIsDisplayed()
253+
composeTestRule
254+
.onNodeWithText("The maximum number of characters that are permitted in the answer is: 10")
255+
.assertIsDisplayed()
247256
}
248257

249258
@Test
250-
@UiThreadTest
251259
fun bind_readOnly_shouldDisableView() {
252260
viewHolder.bind(
253261
QuestionnaireViewItem(
@@ -257,11 +265,7 @@ class PhoneNumberViewHolderFactoryInstrumentedTest {
257265
answersChangedCallback = { _, _, _, _ -> },
258266
),
259267
)
260-
261-
assertThat(
262-
viewHolder.itemView.findViewById<TextInputEditText>(R.id.text_input_edit_text).isEnabled,
263-
)
264-
.isFalse()
268+
composeTestRule.onNodeWithTag(EDIT_TEXT_FIELD_TEST_TAG).assertIsNotEnabled()
265269
}
266270

267271
/** Method to set content view for test activity */

0 commit comments

Comments
 (0)