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
18 
19 import androidx.compose.runtime.LaunchedEffect
20 import androidx.compose.runtime.MonotonicFrameClock
21 import androidx.compose.runtime.Recomposer
22 import androidx.compose.ui.test.internal.JvmDefaultWithCompatibility
23 
24 /**
25  * The clock that drives [frames][MonotonicFrameClock.withFrameNanos], [recompositions][Recomposer]
26  * and [launched effects][LaunchedEffect] in compose tests.
27  *
28  * This clock is ultimately responsible for driving all recompositions, all subscribers to
29  * [withFrameNanos][MonotonicFrameClock.withFrameNanos] (all compose animations) and all coroutines
30  * launched with [LaunchedEffect][LaunchedEffect] (for example gesture detection). It is important
31  * to realize that if this clock does not tick, recomposition will not happen and animations are
32  * frozen.
33  *
34  * Measure, layout and draw passes may be driven by this clock, depending on the platform. On
35  * Desktop, these are all driven by this clock, but on Android only measure and layout are performed
36  * synchronously. The draw pass on Android is driven by the Choreographer and will not happen as a
37  * result of forwarding this clock. Note that the Choreographer will also perform measure and layout
38  * passes, but these are mostly redundant because we will already have done the measure and layout
39  * pass as a result of forwarding this clock to recompose. That means that measure, layout and draw
40  * passes can still occur on Android even if this clock is paused.
41  *
42  * Therefore, when setting [autoAdvance] to `false` and taking control over this clock, there are
43  * several things to realize:
44  * * Recomposition can only happen when a frame is produced by this clock, with one exception: the
45  *   initial composition when calling setContent happens immediately.
46  * * Callers of [withFrameNanos][MonotonicFrameClock.withFrameNanos] can only get a frame time when
47  *   a frame is produced by this clock.
48  * * If there is both a pending recomposition and an animation awaiting a
49  *   [frame time][MonotonicFrameClock.withFrameNanos], ticking this clock will _first_ send the new
50  *   frame time to the animation, and _then_ perform recomposition. Any state changes made by the
51  *   animation will be seen by the recomposition.
52  * * Because animations receive their [frame time][MonotonicFrameClock.withFrameNanos] _before_
53  *   recomposition, an animation will not get its start time in the first frame after kicking it off
54  *   by toggling a state variable. For example, with a frame time of 16ms; when you call
55  *   [advanceTimeBy(32)][advanceTimeBy] after you toggled a state variable to kick off an animation,
56  *   the animation's play time will still be at 0ms. The first frame is produced when the clock has
57  *   advanced 16ms and will run a recomposition. During that recomposition the animation will be
58  *   scheduled to start. When the clock has advanced another 16ms, the animation gets its first
59  *   frame time and initialize the play time to `t=0`.
60  * * Because animations request the next [frame][MonotonicFrameClock.withFrameNanos] during the
61  *   current frame, calling [advanceTimeBy(160)][advanceTimeBy] while an animation is running will
62  *   produce 10 frames of 16ms rather than 1 frame of 160ms (assuming a frame time of 16ms). Measure
63  *   and layout happens after each frame, but draw will not happen in between these frames.
64  * * After modifying a state variable, recomposition needs to happen to reflect the new state in the
65  *   UI. Advancing the clock by [one frame][advanceTimeByFrame] will commit the changes and run
66  *   exactly one recomposition and measure and layout if triggered.
67  * * If, after any call to [advanceTimeBy], you want to assert anything related to rendering (e.g.
68  *   [SemanticsNodeInteraction.captureToImage]), you will need a call to
69  *   [waitForIdle][androidx.compose.ui.test.junit4.ComposeTestRule.waitForIdle] or
70  *   [runOnIdle][androidx.compose.ui.test.junit4.ComposeTestRule.runOnIdle] to make sure that any
71  *   triggered draw pass has been completed.
72  * * If you change a state variable that is read during draw, calling [advanceTimeBy] will not
73  *   produce the desired update to the UI. Use
74  *   [waitForIdle][androidx.compose.ui.test.junit4.ComposeTestRule.waitForIdle] for this case.
75  * * [delayed][kotlinx.coroutines.delay] [LaunchedEffect]s are resumed on their scheduled time. That
76  *   means that code like `repeat(2) { delay(1000) }` will complete with a single call to
77  *   [advanceTimeBy(2000)][advanceTimeBy].
78  */
79 @JvmDefaultWithCompatibility
80 interface MainTestClock {
81     /** The current time of this clock in milliseconds. */
82     val currentTime: Long
83 
84     /**
85      * Whether the clock should be advanced by the testing framework while awaiting idleness in
86      * order to process any pending work that is driven by this clock. This ensures that when the
87      * app is [idle][androidx.compose.ui.test.junit4.ComposeTestRule.waitForIdle], there are no more
88      * pending recompositions or ongoing animations.
89      *
90      * If [autoAdvance] is false, the clock is not advanced while awaiting idleness. Moreover,
91      * having pending recompositions or animations is not taken as a sign of pending work
92      * (non-idleness) when awaiting idleness, as waiting for a longer time will not make them
93      * happen. Note that pending measure, layout or draw passes will still be awaited when awaiting
94      * idleness and having [autoAdvance] set to false, as those passes are not driven by this clock.
95      *
96      * By default this is true.
97      */
98     var autoAdvance: Boolean
99 
100     /** [Advances][advanceTimeBy] the main clock by the duration of one frame. */
advanceTimeByFramenull101     fun advanceTimeByFrame()
102 
103     /**
104      * Advances the clock by the given [duration][milliseconds]. The duration is rounded up to the
105      * nearest multiple of the frame duration by default to always produce the same number of frames
106      * regardless of the current time of the clock. Use [ignoreFrameDuration] to disable this
107      * behavior. The frame duration is platform dependent. For example, on a JVM (Android and
108      * Desktop) it is 16ms. Note that if [ignoreFrameDuration] is true, the last few milliseconds
109      * that are advanced might not be observed by anyone, since most processes are only triggered
110      * when a frame is produced.
111      *
112      * When using this method to advance the time by several frames in one invocation, recomposition
113      * is done after each produced frame, but whether measure, layout and draw happen is platform
114      * dependent. On Android, measure and layout will be done, but not draw. On Desktop, measure,
115      * layout and draw will happen. Frames are only produced if there is a need for them, e.g. when
116      * an animation is running, or if state is changed. See [MainTestClock] for a more in depth
117      * explanation of the behavior of your test when controlling the clock.
118      *
119      * It is recommended to set [autoAdvance] to false when using this method, but it is not
120      * strictly necessary. Manually advancing the time is just as fast as automatic advancement and
121      * vice versa.
122      *
123      * @param milliseconds The minimal duration to advance the main clock by. Will be rounded up to
124      *   the nearest frame duration, unless [ignoreFrameDuration] is `true`.
125      * @param ignoreFrameDuration Whether to avoid rounding up the [milliseconds] to the nearest
126      *   multiple of the frame duration. `false` by default.
127      */
128     fun advanceTimeBy(milliseconds: Long, ignoreFrameDuration: Boolean = false)
129 
130     /**
131      * Advances the clock in increments of a [single frame][advanceTimeByFrame] until the given
132      * [condition] is satisfied.
133      *
134      * Note that the condition should only rely on things that are driven by this clock. Depending
135      * on the platform, measure, layout or draw passes might not happen in between advancements of
136      * the clock while waiting for the condition to become true. On Android and Desktop, measure and
137      * layout are happening, but draw only happens on Desktop. If your condition relies on the
138      * result of draw, use [waitUntil][androidx.compose.ui.test.junit4.ComposeTestRule.waitUntil]
139      * instead.
140      *
141      * See [MainTestClock] for a thorough explanation of what is and what isn't going to happen as a
142      * result of a call to `advanceTimeBy`.
143      *
144      * @param timeoutMillis The time after which this method throws an exception if the given
145      *   condition is not satisfied. This is test clock time, not the wall clock or cpu time.
146      * @param condition A function returning true if the condition is satisfied and false if it is
147      *   not.
148      * @throws ComposeTimeoutException the condition is not satisfied after [timeoutMillis].
149      */
150     fun advanceTimeUntil(timeoutMillis: Long = 1_000, condition: () -> Boolean)
151 }
152 
153 /** Thrown in cases where Compose test can't satisfy a condition in a defined time limit. */
154 class ComposeTimeoutException(message: String?) : Throwable(message)
155