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