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