Skip to content

Commit 11aa9c1

Browse files
authored
Migrate AddEditTasks screen to Compose (#823)
1 parent 653a563 commit 11aa9c1

File tree

8 files changed

+430
-340
lines changed

8 files changed

+430
-340
lines changed

app/src/androidTestMock/java/com/example/android/architecture/blueprints/todoapp/tasks/TasksActivityTest.kt

Lines changed: 63 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
*/
1616
package com.example.android.architecture.blueprints.todoapp.tasks
1717

18-
import androidx.test.core.app.ActivityScenario
18+
import androidx.compose.ui.test.SemanticsNodeInteraction
19+
import androidx.compose.ui.test.hasSetTextAction
20+
import androidx.compose.ui.test.hasText
21+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
22+
import androidx.compose.ui.test.onNodeWithContentDescription
23+
import androidx.compose.ui.test.performClick
24+
import androidx.compose.ui.test.performTextInput
25+
import androidx.compose.ui.test.performTextReplacement
1926
import androidx.test.core.app.ApplicationProvider.getApplicationContext
2027
import androidx.test.espresso.Espresso.onView
2128
import androidx.test.espresso.IdlingRegistry
2229
import androidx.test.espresso.action.ViewActions.click
23-
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
24-
import androidx.test.espresso.action.ViewActions.replaceText
25-
import androidx.test.espresso.action.ViewActions.typeText
2630
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
2731
import androidx.test.espresso.assertion.ViewAssertions.matches
2832
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
@@ -35,7 +39,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
3539
import androidx.test.filters.LargeTest
3640
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
3741
import com.example.android.architecture.blueprints.todoapp.R
38-
import com.example.android.architecture.blueprints.todoapp.R.string
3942
import com.example.android.architecture.blueprints.todoapp.ServiceLocator
4043
import com.example.android.architecture.blueprints.todoapp.data.Task
4144
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
@@ -48,21 +51,23 @@ import org.hamcrest.Matchers.allOf
4851
import org.hamcrest.core.IsNot.not
4952
import org.junit.After
5053
import org.junit.Before
54+
import org.junit.Rule
5155
import org.junit.Test
5256
import org.junit.runner.RunWith
5357

5458
/**
5559
* Large End-to-End test for the tasks module.
56-
*
57-
* UI tests usually use [ActivityTestRule] but there's no API to perform an action before
58-
* each test. The workaround is to use `ActivityScenario.launch()` and `ActivityScenario.close()`.
5960
*/
6061
@RunWith(AndroidJUnit4::class)
6162
@LargeTest
6263
class TasksActivityTest {
6364

6465
private lateinit var repository: TasksRepository
6566

67+
@get:Rule
68+
val composeTestRule = createAndroidComposeRule<TasksActivity>()
69+
private val activity by lazy { composeTestRule.activity }
70+
6671
// An Idling Resource that waits for Data Binding to have no pending bindings
6772
private val dataBindingIdlingResource = DataBindingIdlingResource()
6873

@@ -104,11 +109,10 @@ class TasksActivityTest {
104109

105110
@Test
106111
fun editTask() {
107-
repository.saveTaskBlocking(Task("TITLE1", "DESCRIPTION"))
112+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
108113

109-
// start up Tasks screen
110-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
111-
dataBindingIdlingResource.monitorActivity(activityScenario)
114+
repository.saveTaskBlocking(Task("TITLE1", "DESCRIPTION"))
115+
composeTestRule.waitForIdle()
112116

113117
// Click on the task on the list and verify that all the data is correct
114118
onView(withText("TITLE1")).perform(click())
@@ -118,31 +122,27 @@ class TasksActivityTest {
118122

119123
// Click on the edit button, edit, and save
120124
onView(withId(R.id.edit_task_fab)).perform(click())
121-
onView(withId(R.id.add_task_title_edit_text)).perform(replaceText("NEW TITLE"))
122-
onView(withId(R.id.add_task_description_edit_text)).perform(replaceText("NEW DESCRIPTION"))
123-
onView(withId(R.id.save_task_fab)).perform(click())
125+
findTextField("TITLE1").performTextReplacement("NEW TITLE")
126+
findTextField("DESCRIPTION").performTextReplacement("NEW DESCRIPTION")
127+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))
128+
.performClick()
124129

125130
// Verify task is displayed on screen in the task list.
126131
onView(withText("NEW TITLE")).check(matches(isDisplayed()))
127132
// Verify previous task is not displayed
128133
onView(withText("TITLE1")).check(doesNotExist())
129-
// Make sure the activity is closed before resetting the db:
130-
activityScenario.close()
131134
}
132135

133136
@Test
134137
fun createOneTask_deleteTask() {
135-
136-
// start up Tasks screen
137-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
138-
dataBindingIdlingResource.monitorActivity(activityScenario)
138+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
139139

140140
// Add active task
141141
onView(withId(R.id.add_task_fab)).perform(click())
142-
onView(withId(R.id.add_task_title_edit_text))
143-
.perform(typeText("TITLE1"), closeSoftKeyboard())
144-
onView(withId(R.id.add_task_description_edit_text)).perform(typeText("DESCRIPTION"))
145-
onView(withId(R.id.save_task_fab)).perform(click())
142+
findTextField(R.string.title_hint).performTextInput("TITLE1")
143+
findTextField(R.string.description_hint).performTextInput("DESCRIPTION")
144+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))
145+
.performClick()
146146

147147
// Open it in details view
148148
onView(withText("TITLE1")).perform(click())
@@ -151,20 +151,17 @@ class TasksActivityTest {
151151

152152
// Verify it was deleted
153153
onView(withId(R.id.menu_filter)).perform(click())
154-
onView(withText(string.nav_all)).perform(click())
154+
onView(withText(R.string.nav_all)).perform(click())
155155
onView(withText("TITLE1")).check(doesNotExist())
156-
// Make sure the activity is closed before resetting the db:
157-
activityScenario.close()
158156
}
159157

160158
@Test
161159
fun createTwoTasks_deleteOneTask() {
160+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
161+
162162
repository.saveTaskBlocking(Task("TITLE1", "DESCRIPTION"))
163163
repository.saveTaskBlocking(Task("TITLE2", "DESCRIPTION"))
164-
165-
// start up Tasks screen
166-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
167-
dataBindingIdlingResource.monitorActivity(activityScenario)
164+
composeTestRule.waitForIdle()
168165

169166
// Open the second task in details view
170167
onView(withText("TITLE2")).perform(click())
@@ -173,22 +170,19 @@ class TasksActivityTest {
173170

174171
// Verify only one task was deleted
175172
onView(withId(R.id.menu_filter)).perform(click())
176-
onView(withText(string.nav_all)).perform(click())
173+
onView(withText(R.string.nav_all)).perform(click())
177174
onView(withText("TITLE1")).check(matches(isDisplayed()))
178175
onView(withText("TITLE2")).check(doesNotExist())
179-
// Make sure the activity is closed before resetting the db:
180-
activityScenario.close()
181176
}
182177

183178
@Test
184179
fun markTaskAsCompleteOnDetailScreen_taskIsCompleteInList() {
180+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
181+
185182
// Add 1 active task
186183
val taskTitle = "COMPLETED"
187184
repository.saveTaskBlocking(Task(taskTitle, "DESCRIPTION"))
188-
189-
// start up Tasks screen
190-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
191-
dataBindingIdlingResource.monitorActivity(activityScenario)
185+
composeTestRule.waitForIdle()
192186

193187
// Click on the task on the list
194188
onView(withText(taskTitle)).perform(click())
@@ -199,26 +193,23 @@ class TasksActivityTest {
199193
// Click on the navigation up button to go back to the list
200194
onView(
201195
withContentDescription(
202-
activityScenario.getToolbarNavigationContentDescription()
196+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
203197
)
204198
).perform(click())
205199

206200
// Check that the task is marked as completed
207201
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
208202
.check(matches(isChecked()))
209-
// Make sure the activity is closed before resetting the db:
210-
activityScenario.close()
211203
}
212204

213205
@Test
214206
fun markTaskAsActiveOnDetailScreen_taskIsActiveInList() {
207+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
208+
215209
// Add 1 completed task
216210
val taskTitle = "ACTIVE"
217211
repository.saveTaskBlocking(Task(taskTitle, "DESCRIPTION", true))
218-
219-
// start up Tasks screen
220-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
221-
dataBindingIdlingResource.monitorActivity(activityScenario)
212+
composeTestRule.waitForIdle()
222213

223214
// Click on the task on the list
224215
onView(withText(taskTitle)).perform(click())
@@ -228,26 +219,23 @@ class TasksActivityTest {
228219
// Click on the navigation up button to go back to the list
229220
onView(
230221
withContentDescription(
231-
activityScenario.getToolbarNavigationContentDescription()
222+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
232223
)
233224
).perform(click())
234225

235226
// Check that the task is marked as active
236227
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
237228
.check(matches(not(isChecked())))
238-
// Make sure the activity is closed before resetting the db:
239-
activityScenario.close()
240229
}
241230

242231
@Test
243232
fun markTaskAsCompleteAndActiveOnDetailScreen_taskIsActiveInList() {
233+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
234+
244235
// Add 1 active task
245236
val taskTitle = "ACT-COMP"
246237
repository.saveTaskBlocking(Task(taskTitle, "DESCRIPTION"))
247-
248-
// start up Tasks screen
249-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
250-
dataBindingIdlingResource.monitorActivity(activityScenario)
238+
composeTestRule.waitForIdle()
251239

252240
// Click on the task on the list
253241
onView(withText(taskTitle)).perform(click())
@@ -259,26 +247,23 @@ class TasksActivityTest {
259247
// Click on the navigation up button to go back to the list
260248
onView(
261249
withContentDescription(
262-
activityScenario.getToolbarNavigationContentDescription()
250+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
263251
)
264252
).perform(click())
265253

266254
// Check that the task is marked as active
267255
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
268256
.check(matches(not(isChecked())))
269-
// Make sure the activity is closed before resetting the db:
270-
activityScenario.close()
271257
}
272258

273259
@Test
274260
fun markTaskAsActiveAndCompleteOnDetailScreen_taskIsCompleteInList() {
261+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
262+
275263
// Add 1 completed task
276264
val taskTitle = "COMP-ACT"
277265
repository.saveTaskBlocking(Task(taskTitle, "DESCRIPTION", true))
278-
279-
// start up Tasks screen
280-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
281-
dataBindingIdlingResource.monitorActivity(activityScenario)
266+
composeTestRule.waitForIdle()
282267

283268
// Click on the task on the list
284269
onView(withText(taskTitle)).perform(click())
@@ -290,33 +275,39 @@ class TasksActivityTest {
290275
// Click on the navigation up button to go back to the list
291276
onView(
292277
withContentDescription(
293-
activityScenario.getToolbarNavigationContentDescription()
278+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
294279
)
295280
).perform(click())
296281

297282
// Check that the task is marked as active
298283
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
299284
.check(matches(isChecked()))
300-
// Make sure the activity is closed before resetting the db:
301-
activityScenario.close()
302285
}
303286

304287
@Test
305288
fun createTask() {
306-
// start up Tasks screen
307-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
308-
dataBindingIdlingResource.monitorActivity(activityScenario)
289+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
309290

310291
// Click on the "+" button, add details, and save
311292
onView(withId(R.id.add_task_fab)).perform(click())
312-
onView(withId(R.id.add_task_title_edit_text))
313-
.perform(typeText("title"), closeSoftKeyboard())
314-
onView(withId(R.id.add_task_description_edit_text)).perform(typeText("description"))
315-
onView(withId(R.id.save_task_fab)).perform(click())
293+
findTextField(R.string.title_hint).performTextInput("title")
294+
findTextField(R.string.description_hint).performTextInput("description")
295+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))
296+
.performClick()
316297

317298
// Then verify task is displayed on screen
318299
onView(withText("title")).check(matches(isDisplayed()))
319-
// Make sure the activity is closed before resetting the db:
320-
activityScenario.close()
300+
}
301+
302+
private fun findTextField(textId: Int): SemanticsNodeInteraction {
303+
return composeTestRule.onNode(
304+
hasSetTextAction() and hasText(activity.getString(textId))
305+
)
306+
}
307+
308+
private fun findTextField(text: String): SemanticsNodeInteraction {
309+
return composeTestRule.onNode(
310+
hasSetTextAction() and hasText(text)
311+
)
321312
}
322313
}

0 commit comments

Comments
 (0)