1+ /*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+ package com.example.android.architecture.blueprints.todoapp
17+
18+ import android.app.Activity
19+ import android.view.Gravity
20+ import androidx.appcompat.widget.Toolbar
21+ import androidx.drawerlayout.widget.DrawerLayout
22+ import androidx.test.core.app.ActivityScenario
23+ import androidx.test.core.app.ApplicationProvider.getApplicationContext
24+ import androidx.test.espresso.Espresso.onView
25+ import androidx.test.espresso.Espresso.pressBack
26+ import androidx.test.espresso.IdlingRegistry
27+ import androidx.test.espresso.action.ViewActions.click
28+ import androidx.test.espresso.assertion.ViewAssertions.matches
29+ import androidx.test.espresso.contrib.DrawerMatchers.isClosed
30+ import androidx.test.espresso.contrib.DrawerMatchers.isOpen
31+ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
32+ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
33+ import androidx.test.espresso.matcher.ViewMatchers.withId
34+ import androidx.test.espresso.matcher.ViewMatchers.withText
35+ import androidx.test.ext.junit.runners.AndroidJUnit4
36+ import androidx.test.filters.LargeTest
37+ import com.example.android.architecture.blueprints.todoapp.data.Task
38+ import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
39+ import com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity
40+ import com.example.android.architecture.blueprints.todoapp.util.DataBindingIdlingResource
41+ import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingResource
42+ import com.example.android.architecture.blueprints.todoapp.util.monitorActivity
43+ import kotlinx.coroutines.runBlocking
44+ import org.junit.After
45+ import org.junit.Before
46+ import org.junit.Test
47+ import org.junit.runner.RunWith
48+
49+ /* *
50+ * Tests for the [DrawerLayout] layout component in [TasksActivity] which manages
51+ * navigation within the app.
52+ *
53+ * UI tests usually use [ActivityTestRule] but there's no API to perform an action before
54+ * each test. The workaround is to use `ActivityScenario.launch()` and `ActivityScenario.close()`.
55+ */
56+ @RunWith(AndroidJUnit4 ::class )
57+ @LargeTest
58+ class AppNavigationTest {
59+
60+ private lateinit var tasksRepository: TasksRepository
61+
62+ // An Idling Resource that waits for Data Binding to have no pending bindings
63+ private val dataBindingIdlingResource = DataBindingIdlingResource ()
64+
65+ @Before
66+ fun init () {
67+ tasksRepository = ServiceLocator .provideTasksRepository(getApplicationContext())
68+ }
69+
70+ @After
71+ fun reset () {
72+ ServiceLocator .resetRepository()
73+ }
74+
75+ /* *
76+ * Idling resources tell Espresso that the app is idle or busy. This is needed when operations
77+ * are not scheduled in the main Looper (for example when executed on a different thread).
78+ */
79+ @Before
80+ fun registerIdlingResource () {
81+ IdlingRegistry .getInstance().register(EspressoIdlingResource .countingIdlingResource)
82+ IdlingRegistry .getInstance().register(dataBindingIdlingResource)
83+ }
84+
85+ /* *
86+ * Unregister your Idling Resource so it can be garbage collected and does not leak any memory.
87+ */
88+ @After
89+ fun unregisterIdlingResource () {
90+ IdlingRegistry .getInstance().unregister(EspressoIdlingResource .countingIdlingResource)
91+ IdlingRegistry .getInstance().unregister(dataBindingIdlingResource)
92+ }
93+
94+ @Test
95+ fun tasksScreen_clickOnAndroidHomeIcon_OpensNavigation () {
96+ // Start up Tasks screen
97+ val activityScenario = ActivityScenario .launch(TasksActivity ::class .java)
98+ dataBindingIdlingResource.monitorActivity(activityScenario)
99+
100+ // Check that left drawer is closed at startup
101+ onView(withId(R .id.drawer_layout))
102+ .check(matches(isClosed(Gravity .START ))) // Left Drawer should be closed.
103+
104+ // Open drawer by clicking drawer icon
105+ onView(
106+ withContentDescription(
107+ activityScenario
108+ .getToolbarNavigationContentDescription()
109+ )
110+ ).perform(click())
111+
112+ // Check if drawer is open
113+ onView(withId(R .id.drawer_layout))
114+ .check(matches(isOpen(Gravity .START ))) // Left drawer is open open.
115+ // When using ActivityScenario.launch, always call close()
116+ activityScenario.close()
117+ }
118+
119+ @Test
120+ fun taskDetailScreen_doubleUpButton () = runBlocking {
121+ val task = Task (" Up <- button" , " Description" )
122+ tasksRepository.saveTask(task)
123+
124+ // Start up Tasks screen
125+ val activityScenario = ActivityScenario .launch(TasksActivity ::class .java)
126+ dataBindingIdlingResource.monitorActivity(activityScenario)
127+
128+ // Click on the task on the list
129+ onView(withText(" Up <- button" )).perform(click())
130+
131+ // Click on the edit task button
132+ onView(withId(R .id.edit_task_fab)).perform(click())
133+
134+ // Confirm that if we click up button once, we end up back at the task details page
135+ onView(
136+ withContentDescription(
137+ activityScenario
138+ .getToolbarNavigationContentDescription()
139+ )
140+ ).perform(click())
141+ onView(withId(R .id.task_detail_title_text)).check(matches(isDisplayed()))
142+
143+ // Confirm that if we click up button a second time, we end up back at the home screen
144+ onView(
145+ withContentDescription(
146+ activityScenario
147+ .getToolbarNavigationContentDescription()
148+ )
149+ ).perform(click())
150+ onView(withId(R .id.tasks_container_layout)).check(matches(isDisplayed()))
151+
152+ // When using ActivityScenario.launch, always call close()
153+ activityScenario.close()
154+ }
155+
156+ @Test
157+ fun taskDetailScreen_doubleBackButton () = runBlocking {
158+ val task = Task (" Back button" , " Description" )
159+ tasksRepository.saveTask(task)
160+
161+ // Start up Tasks screen
162+ val activityScenario = ActivityScenario .launch(TasksActivity ::class .java)
163+ dataBindingIdlingResource.monitorActivity(activityScenario)
164+
165+ // Click on the task on the list
166+ onView(withText(" Back button" )).perform(click())
167+
168+ // Click on the edit task button
169+ onView(withId(R .id.edit_task_fab)).perform(click())
170+
171+ // Confirm that if we click back once, we end up back at the task details page
172+ pressBack()
173+ onView(withId(R .id.task_detail_title_text)).check(matches(isDisplayed()))
174+
175+ // Confirm that if we click back a second time, we end up back at the home screen
176+ pressBack()
177+ onView(withId(R .id.tasks_container_layout)).check(matches(isDisplayed()))
178+
179+ // When using ActivityScenario.launch, always call close()
180+ activityScenario.close()
181+ }
182+ }
183+
184+ fun <T : Activity > ActivityScenario<T>.getToolbarNavigationContentDescription ()
185+ : String {
186+ var description = " "
187+ onActivity {
188+ description =
189+ it.findViewById<Toolbar >(R .id.toolbar).navigationContentDescription as String
190+ }
191+ return description
192+ }
0 commit comments