15
15
*/
16
16
package com.example.android.architecture.blueprints.todoapp.tasks
17
17
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
19
26
import androidx.test.core.app.ApplicationProvider.getApplicationContext
20
27
import androidx.test.espresso.Espresso.onView
21
28
import androidx.test.espresso.IdlingRegistry
22
29
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
26
30
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
27
31
import androidx.test.espresso.assertion.ViewAssertions.matches
28
32
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
@@ -35,7 +39,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
35
39
import androidx.test.filters.LargeTest
36
40
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
37
41
import com.example.android.architecture.blueprints.todoapp.R
38
- import com.example.android.architecture.blueprints.todoapp.R.string
39
42
import com.example.android.architecture.blueprints.todoapp.ServiceLocator
40
43
import com.example.android.architecture.blueprints.todoapp.data.Task
41
44
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
@@ -48,21 +51,23 @@ import org.hamcrest.Matchers.allOf
48
51
import org.hamcrest.core.IsNot.not
49
52
import org.junit.After
50
53
import org.junit.Before
54
+ import org.junit.Rule
51
55
import org.junit.Test
52
56
import org.junit.runner.RunWith
53
57
54
58
/* *
55
59
* 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()`.
59
60
*/
60
61
@RunWith(AndroidJUnit4 ::class )
61
62
@LargeTest
62
63
class TasksActivityTest {
63
64
64
65
private lateinit var repository: TasksRepository
65
66
67
+ @get:Rule
68
+ val composeTestRule = createAndroidComposeRule<TasksActivity >()
69
+ private val activity by lazy { composeTestRule.activity }
70
+
66
71
// An Idling Resource that waits for Data Binding to have no pending bindings
67
72
private val dataBindingIdlingResource = DataBindingIdlingResource ()
68
73
@@ -104,11 +109,10 @@ class TasksActivityTest {
104
109
105
110
@Test
106
111
fun editTask () {
107
- repository.saveTaskBlocking( Task ( " TITLE1 " , " DESCRIPTION " ) )
112
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario )
108
113
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()
112
116
113
117
// Click on the task on the list and verify that all the data is correct
114
118
onView(withText(" TITLE1" )).perform(click())
@@ -118,31 +122,27 @@ class TasksActivityTest {
118
122
119
123
// Click on the edit button, edit, and save
120
124
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()
124
129
125
130
// Verify task is displayed on screen in the task list.
126
131
onView(withText(" NEW TITLE" )).check(matches(isDisplayed()))
127
132
// Verify previous task is not displayed
128
133
onView(withText(" TITLE1" )).check(doesNotExist())
129
- // Make sure the activity is closed before resetting the db:
130
- activityScenario.close()
131
134
}
132
135
133
136
@Test
134
137
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)
139
139
140
140
// Add active task
141
141
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( )
146
146
147
147
// Open it in details view
148
148
onView(withText(" TITLE1" )).perform(click())
@@ -151,20 +151,17 @@ class TasksActivityTest {
151
151
152
152
// Verify it was deleted
153
153
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())
155
155
onView(withText(" TITLE1" )).check(doesNotExist())
156
- // Make sure the activity is closed before resetting the db:
157
- activityScenario.close()
158
156
}
159
157
160
158
@Test
161
159
fun createTwoTasks_deleteOneTask () {
160
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
161
+
162
162
repository.saveTaskBlocking(Task (" TITLE1" , " DESCRIPTION" ))
163
163
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()
168
165
169
166
// Open the second task in details view
170
167
onView(withText(" TITLE2" )).perform(click())
@@ -173,22 +170,19 @@ class TasksActivityTest {
173
170
174
171
// Verify only one task was deleted
175
172
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())
177
174
onView(withText(" TITLE1" )).check(matches(isDisplayed()))
178
175
onView(withText(" TITLE2" )).check(doesNotExist())
179
- // Make sure the activity is closed before resetting the db:
180
- activityScenario.close()
181
176
}
182
177
183
178
@Test
184
179
fun markTaskAsCompleteOnDetailScreen_taskIsCompleteInList () {
180
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
181
+
185
182
// Add 1 active task
186
183
val taskTitle = " COMPLETED"
187
184
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()
192
186
193
187
// Click on the task on the list
194
188
onView(withText(taskTitle)).perform(click())
@@ -199,26 +193,23 @@ class TasksActivityTest {
199
193
// Click on the navigation up button to go back to the list
200
194
onView(
201
195
withContentDescription(
202
- activityScenario .getToolbarNavigationContentDescription()
196
+ composeTestRule.activityRule.scenario .getToolbarNavigationContentDescription()
203
197
)
204
198
).perform(click())
205
199
206
200
// Check that the task is marked as completed
207
201
onView(allOf(withId(R .id.complete_checkbox), hasSibling(withText(taskTitle))))
208
202
.check(matches(isChecked()))
209
- // Make sure the activity is closed before resetting the db:
210
- activityScenario.close()
211
203
}
212
204
213
205
@Test
214
206
fun markTaskAsActiveOnDetailScreen_taskIsActiveInList () {
207
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
208
+
215
209
// Add 1 completed task
216
210
val taskTitle = " ACTIVE"
217
211
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()
222
213
223
214
// Click on the task on the list
224
215
onView(withText(taskTitle)).perform(click())
@@ -228,26 +219,23 @@ class TasksActivityTest {
228
219
// Click on the navigation up button to go back to the list
229
220
onView(
230
221
withContentDescription(
231
- activityScenario .getToolbarNavigationContentDescription()
222
+ composeTestRule.activityRule.scenario .getToolbarNavigationContentDescription()
232
223
)
233
224
).perform(click())
234
225
235
226
// Check that the task is marked as active
236
227
onView(allOf(withId(R .id.complete_checkbox), hasSibling(withText(taskTitle))))
237
228
.check(matches(not (isChecked())))
238
- // Make sure the activity is closed before resetting the db:
239
- activityScenario.close()
240
229
}
241
230
242
231
@Test
243
232
fun markTaskAsCompleteAndActiveOnDetailScreen_taskIsActiveInList () {
233
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
234
+
244
235
// Add 1 active task
245
236
val taskTitle = " ACT-COMP"
246
237
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()
251
239
252
240
// Click on the task on the list
253
241
onView(withText(taskTitle)).perform(click())
@@ -259,26 +247,23 @@ class TasksActivityTest {
259
247
// Click on the navigation up button to go back to the list
260
248
onView(
261
249
withContentDescription(
262
- activityScenario .getToolbarNavigationContentDescription()
250
+ composeTestRule.activityRule.scenario .getToolbarNavigationContentDescription()
263
251
)
264
252
).perform(click())
265
253
266
254
// Check that the task is marked as active
267
255
onView(allOf(withId(R .id.complete_checkbox), hasSibling(withText(taskTitle))))
268
256
.check(matches(not (isChecked())))
269
- // Make sure the activity is closed before resetting the db:
270
- activityScenario.close()
271
257
}
272
258
273
259
@Test
274
260
fun markTaskAsActiveAndCompleteOnDetailScreen_taskIsCompleteInList () {
261
+ dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
262
+
275
263
// Add 1 completed task
276
264
val taskTitle = " COMP-ACT"
277
265
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()
282
267
283
268
// Click on the task on the list
284
269
onView(withText(taskTitle)).perform(click())
@@ -290,33 +275,39 @@ class TasksActivityTest {
290
275
// Click on the navigation up button to go back to the list
291
276
onView(
292
277
withContentDescription(
293
- activityScenario .getToolbarNavigationContentDescription()
278
+ composeTestRule.activityRule.scenario .getToolbarNavigationContentDescription()
294
279
)
295
280
).perform(click())
296
281
297
282
// Check that the task is marked as active
298
283
onView(allOf(withId(R .id.complete_checkbox), hasSibling(withText(taskTitle))))
299
284
.check(matches(isChecked()))
300
- // Make sure the activity is closed before resetting the db:
301
- activityScenario.close()
302
285
}
303
286
304
287
@Test
305
288
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)
309
290
310
291
// Click on the "+" button, add details, and save
311
292
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( )
316
297
317
298
// Then verify task is displayed on screen
318
299
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
+ )
321
312
}
322
313
}
0 commit comments