Skip to content

Commit 7a2bb8d

Browse files
authored
Add setContent variant which returns initial snapshot (#2548)
1 parent 1161f6e commit 7a2bb8d

File tree

5 files changed

+59
-0
lines changed

5 files changed

+59
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Breaking:
1010
New:
1111
- `UIConfiguration.viewInsets` tracks the safe area of the specific `RedwoodView` being targeted. This is currently implemented for views on Android and UIViews on iOS.
1212
- `ConsumeInsets {}` composable consumes insets. Most applications should call this in their root composable function.
13+
- Add `TestRedwoodComposition.setContentAndSnapshot` function which is a fused version of `setContent` and `awaitSnapshot`, except that it guarantees the returned snapshot is the result of the initial composition of the content without any additional frames sent.
1314

1415
Changed:
1516
- Nothing yet!

redwood-testing/api/redwood-testing.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public abstract interface class app/cash/redwood/testing/TestRedwoodComposition
99
public static synthetic fun awaitSnapshot-VtjQ1oo$default (Lapp/cash/redwood/testing/TestRedwoodComposition;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
1010
public abstract fun getUiConfigurations ()Lkotlinx/coroutines/flow/MutableStateFlow;
1111
public abstract fun saveState ()Lapp/cash/redwood/testing/TestSavedState;
12+
public abstract fun setContentAndSnapshot (Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
1213
}
1314

1415
public final class app/cash/redwood/testing/TestRedwoodCompositionKt {

redwood-testing/api/redwood-testing.klib.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ abstract interface <#A: kotlin/Any?> app.cash.redwood.testing/TestRedwoodComposi
1111
abstract fun <get-uiConfigurations>(): kotlinx.coroutines.flow/MutableStateFlow<app.cash.redwood.ui/UiConfiguration> // app.cash.redwood.testing/TestRedwoodComposition.uiConfigurations.<get-uiConfigurations>|<get-uiConfigurations>(){}[0]
1212

1313
abstract fun saveState(): app.cash.redwood.testing/TestSavedState // app.cash.redwood.testing/TestRedwoodComposition.saveState|saveState(){}[0]
14+
abstract fun setContentAndSnapshot(kotlin/Function2<androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>): #A // app.cash.redwood.testing/TestRedwoodComposition.setContentAndSnapshot|setContentAndSnapshot(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}[0]
1415
abstract suspend fun awaitSnapshot(kotlin.time/Duration = ...): #A // app.cash.redwood.testing/TestRedwoodComposition.awaitSnapshot|awaitSnapshot(kotlin.time.Duration){}[0]
1516
}
1617

redwood-testing/src/commonMain/kotlin/app/cash/redwood/testing/TestRedwoodComposition.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,16 @@ public fun <W : Any, S> TestRedwoodComposition(
6060
}
6161

6262
public interface TestRedwoodComposition<S> : RedwoodComposition {
63+
/**
64+
* A fused call which does both [setContent] and [awaitSnapshot], but without sending a frame
65+
* to the composition. The snapshot returned will always be the result of the synchronous
66+
* recomposition of [content].
67+
*/
68+
public fun setContentAndSnapshot(content: @Composable () -> Unit): S
69+
6370
/**
6471
* Returns a snapshot, waiting if necessary for changes to occur since the previous snapshot.
72+
* Each call to this function is guaranteed to send at least once frame to the composition.
6573
*
6674
* @throws TimeoutCancellationException if no new snapshot is produced before [timeout].
6775
*/
@@ -128,6 +136,13 @@ private class RealTestRedwoodComposition<W : Any, S>(
128136
composition.setContent(content)
129137
}
130138

139+
override fun setContentAndSnapshot(content: @Composable () -> Unit): S {
140+
setContent(content)
141+
check(hasChanges)
142+
hasChanges = false
143+
return createSnapshot()
144+
}
145+
131146
override suspend fun awaitSnapshot(timeout: Duration): S {
132147
check(contentSet) { "setContent must be called first!" }
133148

redwood-testing/src/commonTest/kotlin/app/cash/redwood/testing/TestRedwoodCompositionTest.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package app.cash.redwood.testing
1717

18+
import androidx.compose.runtime.LaunchedEffect
1819
import androidx.compose.runtime.getValue
1920
import androidx.compose.runtime.movableContentOf
21+
import androidx.compose.runtime.mutableIntStateOf
2022
import androidx.compose.runtime.mutableStateOf
2123
import androidx.compose.runtime.remember
2224
import androidx.compose.runtime.setValue
@@ -28,12 +30,51 @@ import app.cash.redwood.widget.MutableListChildren
2830
import assertk.assertThat
2931
import assertk.assertions.isEqualTo
3032
import com.example.redwood.testapp.compose.Text
33+
import com.example.redwood.testapp.testing.TestSchemaTester
3134
import com.example.redwood.testapp.testing.TestSchemaTestingWidgetFactory
35+
import com.example.redwood.testapp.testing.TextValue
3236
import com.example.redwood.testapp.widget.TestSchemaWidgetSystem
3337
import kotlin.test.Test
38+
import kotlin.time.Duration.Companion.milliseconds
39+
import kotlinx.coroutines.delay
3440
import kotlinx.coroutines.test.runTest
3541

3642
class TestRedwoodCompositionTest {
43+
@Test fun setContentAndSnapshot() = runTest {
44+
TestSchemaTester {
45+
var number by mutableIntStateOf(0)
46+
val initial = setContentAndSnapshot {
47+
Text("The number is: $number")
48+
LaunchedEffect(Unit) {
49+
number = 1
50+
}
51+
}
52+
53+
// Defer to allow effect to run.
54+
delay(10.milliseconds)
55+
56+
assertThat(initial.single()).isEqualTo(TextValue(text = "The number is: 0"))
57+
assertThat(awaitSnapshot().single()).isEqualTo(TextValue(text = "The number is: 1"))
58+
}
59+
}
60+
61+
@Test fun setContentThenAwaitSnapshot() = runTest {
62+
TestSchemaTester {
63+
var number by mutableIntStateOf(0)
64+
setContent {
65+
Text("The number is: $number")
66+
LaunchedEffect(Unit) {
67+
number = 1
68+
}
69+
}
70+
71+
// Defer to allow effect to run.
72+
delay(10.milliseconds)
73+
74+
assertThat(awaitSnapshot().single()).isEqualTo(TextValue(text = "The number is: 1"))
75+
}
76+
}
77+
3778
@Test fun awaitSnapshotCapturesMultipleChanges() = runTest {
3879
var count = 0
3980
val tester = TestRedwoodComposition(

0 commit comments

Comments
 (0)