1 /*
2  * Copyright 2020 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 
17 package androidx.compose.ui.test.junit4
18 
19 import androidx.compose.runtime.Composable
20 import androidx.compose.ui.test.ExperimentalTestApi
21 import androidx.compose.ui.test.IdlingResource
22 import androidx.compose.ui.test.MainTestClock
23 import androidx.compose.ui.test.SemanticsMatcher
24 import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
25 import androidx.compose.ui.unit.Density
26 import kotlin.coroutines.CoroutineContext
27 import kotlin.coroutines.EmptyCoroutineContext
28 import kotlinx.coroutines.test.TestCoroutineScheduler
29 import kotlinx.coroutines.test.TestDispatcher
30 import org.junit.rules.TestRule
31 
32 /**
33  * A [TestRule] that allows you to test and control composables, either in isolation or in
34  * applications. Most of the functionality in this interface provides some form of test
35  * synchronization: the test will block until the app or composable is idle, to ensure the tests are
36  * deterministic.
37  *
38  * For example, if you would perform a click on the center of the screen while a button is animating
39  * from left to right over the screen, without synchronization the test would sometimes click when
40  * the button is in the middle of the screen (button is clicked), and sometimes when the button is
41  * past the middle of the screen (button is not clicked). With synchronization, the app would not be
42  * idle until the animation is over, so the test will always click when the button is past the
43  * middle of the screen (and not click it). If you actually do want to click the button when it's in
44  * the middle of the animation, you can do so by controlling the [clock][mainClock]. You'll have to
45  * disable [automatic advancing][MainTestClock.autoAdvance], and manually advance the clock by the
46  * time necessary to position the button in the middle of the screen.
47  *
48  * An instance of [ComposeTestRule] can be created with [createComposeRule], which will also create
49  * a host for the compose content for you (see [ComposeContentTestRule]). If you need to specify
50  * which particular Activity is started on Android, you can use [createAndroidComposeRule].
51  *
52  * If you don't want any Activity to be started automatically by the test rule on Android, you can
53  * use [createEmptyComposeRule]. In such a case, you will have to set content using one of Compose
54  * UI's setters (like [ComponentActivity.setContent][androidx.compose.ui.platform .setContent]).
55  */
56 @JvmDefaultWithCompatibility
57 interface ComposeTestRule : TestRule, SemanticsNodeInteractionsProvider {
58     /**
59      * Current device screen's density. Note that it is technically possible for a Compose hierarchy
60      * to define a different density for a certain subtree.
61      */
62     val density: Density
63 
64     /** Clock that drives frames and recompositions in compose tests. */
65     val mainClock: MainTestClock
66 
67     /**
68      * Runs the given [action] on the UI thread.
69      *
70      * This method is blocking until the action is complete.
71      */
runOnUiThreadnull72     fun <T> runOnUiThread(action: () -> T): T
73 
74     /**
75      * Executes the given [action] in the same way as [runOnUiThread] but [waits][waitForIdle] until
76      * the app is idle before executing the action. This is the recommended way of doing your
77      * assertions on shared variables.
78      *
79      * This method blocks until the action is complete.
80      */
81     fun <T> runOnIdle(action: () -> T): T
82 
83     /**
84      * Waits for the UI to become idle. Quiescence is reached when there are no more pending changes
85      * (e.g. pending recompositions or a pending draw call) and all [IdlingResource]s are idle.
86      *
87      * If [auto advancement][MainTestClock.autoAdvance] is enabled on the [mainClock], this method
88      * will advance the clock to process any pending composition, invalidation and animation. If
89      * auto advancement is not enabled, the clock will not be advanced which means that the Compose
90      * UI appears to be frozen. This is ideal for testing animations in a deterministic way. This
91      * method will always wait for all [IdlingResource]s to become idle.
92      *
93      * Note that some processes are driven by the host operating system and will therefore still
94      * execute when auto advancement is disabled. For example, Android's measure, layout and draw
95      * passes can still happen if required by the View system.
96      */
97     fun waitForIdle()
98 
99     /**
100      * Suspends until the UI is idle. Quiescence is reached when there are no more pending changes
101      * (e.g. pending recompositions or a pending draw call) and all [IdlingResource]s are idle.
102      *
103      * If [auto advancement][MainTestClock.autoAdvance] is enabled on the [mainClock], this method
104      * will advance the clock to process any pending composition, invalidation and animation. If
105      * auto advancement is not enabled, the clock will not be advanced which means that the Compose
106      * UI appears to be frozen. This is ideal for testing animations in a deterministic way. This
107      * method will always wait for all [IdlingResource]s to become idle.
108      *
109      * Note that some processes are driven by the host operating system and will therefore still
110      * execute when auto advancement is disabled. For example, Android's measure, layout and draw
111      * passes can still happen if required by the View system.
112      */
113     suspend fun awaitIdle()
114 
115     /**
116      * Blocks until the given [condition] is satisfied.
117      *
118      * If [auto advancement][MainTestClock.autoAdvance] is enabled on the [mainClock], this method
119      * will actively advance the clock to process any pending composition, invalidation and
120      * animation. If auto advancement is not enabled, the clock will not be advanced actively which
121      * means that the Compose UI appears to be frozen. It is still valid to use this method in this
122      * way, if the condition will be satisfied by something not driven by our clock.
123      *
124      * Compared to [MainTestClock.advanceTimeUntil], [waitUntil] sleeps after every iteration to
125      * yield to other processes. This gives [waitUntil] a better integration with the host, but it
126      * is less preferred from a performance viewpoint. Therefore, we recommend that you try using
127      * [MainTestClock.advanceTimeUntil] before resorting to [waitUntil].
128      *
129      * @param timeoutMillis The time after which this method throws an exception if the given
130      *   condition is not satisfied. This observes wall clock time, not
131      *   [test clock time][mainClock].
132      * @param condition Condition that must be satisfied in order for this method to successfully
133      *   finish.
134      * @throws androidx.compose.ui.test.ComposeTimeoutException If the condition is not satisfied
135      *   after [timeoutMillis] (in wall clock time).
136      */
137     fun waitUntil(timeoutMillis: Long = 1_000, condition: () -> Boolean)
138 
139     /**
140      * Blocks until the given [condition] is satisfied.
141      *
142      * If [auto advancement][MainTestClock.autoAdvance] is enabled on the [mainClock], this method
143      * will actively advance the clock to process any pending composition, invalidation and
144      * animation. If auto advancement is not enabled, the clock will not be advanced actively which
145      * means that the Compose UI appears to be frozen. It is still valid to use this method in this
146      * way, if the condition will be satisfied by something not driven by our clock.
147      *
148      * Compared to [MainTestClock.advanceTimeUntil], [waitUntil] sleeps after every iteration to
149      * yield to other processes. This gives [waitUntil] a better integration with the host, but it
150      * is less preferred from a performance viewpoint. Therefore, we recommend that you try using
151      * [MainTestClock.advanceTimeUntil] before resorting to [waitUntil].
152      *
153      * @param conditionDescription An optional human-readable description of [condition] that will
154      *   be included in the timeout exception if thrown.
155      * @param timeoutMillis The time after which this method throws an exception if the given
156      *   condition is not satisfied. This observes wall clock time, not
157      *   [test clock time][mainClock].
158      * @param condition Condition that must be satisfied in order for this method to successfully
159      *   finish.
160      * @throws androidx.compose.ui.test.ComposeTimeoutException If the condition is not satisfied
161      *   after [timeoutMillis] (in wall clock time).
162      */
163     fun waitUntil(
164         conditionDescription: String,
165         timeoutMillis: Long = 1_000,
166         condition: () -> Boolean
167     ) {
168         waitUntil(timeoutMillis, condition)
169     }
170 
171     /**
172      * Blocks until the number of nodes matching the given [matcher] is equal to the given [count].
173      *
174      * @param matcher The matcher that will be used to filter nodes.
175      * @param count The number of nodes that are expected to
176      * @param timeoutMillis The time after which this method throws an exception if the number of
177      *   nodes that match the [matcher] is not [count]. This observes wall clock time, not frame
178      *   time.
179      * @throws androidx.compose.ui.test.ComposeTimeoutException If the number of nodes that match
180      *   the [matcher] is not [count] after [timeoutMillis] (in wall clock time).
181      * @see ComposeTestRule.waitUntil
182      */
183     @ExperimentalTestApi
waitUntilNodeCountnull184     fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeoutMillis: Long = 1_000L)
185 
186     /**
187      * Blocks until at least one node matches the given [matcher].
188      *
189      * @param matcher The matcher that will be used to filter nodes.
190      * @param timeoutMillis The time after which this method throws an exception if no nodes match
191      *   the given [matcher]. This observes wall clock time, not frame time.
192      * @throws androidx.compose.ui.test.ComposeTimeoutException If no nodes match the given
193      *   [matcher] after [timeoutMillis] (in wall clock time).
194      * @see ComposeTestRule.waitUntil
195      */
196     @ExperimentalTestApi
197     fun waitUntilAtLeastOneExists(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
198 
199     /**
200      * Blocks until exactly one node matches the given [matcher].
201      *
202      * @param matcher The matcher that will be used to filter nodes.
203      * @param timeoutMillis The time after which this method throws an exception if exactly one node
204      *   does not match the given [matcher]. This observes wall clock time, not frame time.
205      * @throws androidx.compose.ui.test.ComposeTimeoutException If exactly one node does not match
206      *   the given [matcher] after [timeoutMillis] (in wall clock time).
207      * @see ComposeTestRule.waitUntil
208      */
209     @ExperimentalTestApi
210     fun waitUntilExactlyOneExists(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
211 
212     /**
213      * Blocks until no nodes match the given [matcher].
214      *
215      * @param matcher The matcher that will be used to filter nodes.
216      * @param timeoutMillis The time after which this method throws an exception if any nodes match
217      *   the given [matcher]. This observes wall clock time, not frame time.
218      * @throws androidx.compose.ui.test.ComposeTimeoutException If any nodes match the given
219      *   [matcher] after [timeoutMillis] (in wall clock time).
220      * @see ComposeTestRule.waitUntil
221      */
222     @ExperimentalTestApi
223     fun waitUntilDoesNotExist(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
224 
225     /** Registers an [IdlingResource] in this test. */
226     fun registerIdlingResource(idlingResource: IdlingResource)
227 
228     /** Unregisters an [IdlingResource] from this test. */
229     fun unregisterIdlingResource(idlingResource: IdlingResource)
230 }
231 
232 /**
233  * A [ComposeTestRule] that allows you to set content without the necessity to provide a host for
234  * the content. The host, such as an Activity, will be created by the test rule.
235  *
236  * An instance of [ComposeContentTestRule] can be created with [createComposeRule]. If you need to
237  * specify which particular Activity is started on Android, you can use [createAndroidComposeRule].
238  *
239  * If you don't want any host to be started automatically by the test rule on Android, you can use
240  * [createEmptyComposeRule]. In such a case, you will have to create a host in your test and set the
241  * content using one of Compose UI's setters (like
242  * [ComponentActivity .setContent][androidx.activity.compose.setContent]).
243  */
244 @JvmDefaultWithCompatibility
245 interface ComposeContentTestRule : ComposeTestRule {
246     /**
247      * Sets the given composable as a content of the current screen.
248      *
249      * Use this in your tests to setup the UI content to be tested. This should be called exactly
250      * once per test.
251      *
252      * @throws IllegalStateException if called more than once per test.
253      */
254     fun setContent(composable: @Composable () -> Unit)
255 }
256 
257 /**
258  * Factory method to provide an implementation of [ComposeContentTestRule].
259  *
260  * This method is useful for tests in compose libraries where it is irrelevant where the compose
261  * content is hosted (e.g. an Activity on Android). Such tests typically set compose content
262  * themselves via [setContent][ComposeContentTestRule.setContent] and only instrument and assert
263  * that content.
264  *
265  * For Android this will use the default Activity (android.app.Activity). You need to add a
266  * reference to this activity into the manifest file of the corresponding tests (usually in
267  * androidTest/AndroidManifest.xml). If your Android test requires a specific Activity to be
268  * launched, see [createAndroidComposeRule].
269  */
createComposeRulenull270 expect fun createComposeRule(): ComposeContentTestRule
271 
272 /**
273  * Factory method to provide an implementation of [ComposeContentTestRule].
274  *
275  * This method is useful for tests in compose libraries where it is irrelevant where the compose
276  * content is hosted (e.g. an Activity on Android). Such tests typically set compose content
277  * themselves via [setContent][ComposeContentTestRule.setContent] and only instrument and assert
278  * that content.
279  *
280  * For Android this will use the default Activity (android.app.Activity). You need to add a
281  * reference to this activity into the manifest file of the corresponding tests (usually in
282  * androidTest/AndroidManifest.xml). If your Android test requires a specific Activity to be
283  * launched, see [createAndroidComposeRule].
284  *
285  * @param effectContext The [CoroutineContext] used to run the composition. The context for
286  *   `LaunchedEffect`s and `rememberCoroutineScope` will be derived from this context. If this
287  *   context contains a [TestDispatcher] or [TestCoroutineScheduler] (in that order), it will be
288  *   used for composition and the [MainTestClock].
289  */
290 @ExperimentalTestApi
291 expect fun createComposeRule(
292     effectContext: CoroutineContext = EmptyCoroutineContext
293 ): ComposeContentTestRule
294