Skip to content

Commit

Permalink
Add setContent variant which returns initial snapshot (#2548)
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeWharton authored Jan 30, 2025
1 parent 1161f6e commit 7a2bb8d
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Breaking:
New:
- `UIConfiguration.viewInsets` tracks the safe area of the specific `RedwoodView` being targeted. This is currently implemented for views on Android and UIViews on iOS.
- `ConsumeInsets {}` composable consumes insets. Most applications should call this in their root composable function.
- 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.

Changed:
- Nothing yet!
Expand Down
1 change: 1 addition & 0 deletions redwood-testing/api/redwood-testing.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public abstract interface class app/cash/redwood/testing/TestRedwoodComposition
public static synthetic fun awaitSnapshot-VtjQ1oo$default (Lapp/cash/redwood/testing/TestRedwoodComposition;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public abstract fun getUiConfigurations ()Lkotlinx/coroutines/flow/MutableStateFlow;
public abstract fun saveState ()Lapp/cash/redwood/testing/TestSavedState;
public abstract fun setContentAndSnapshot (Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
}

public final class app/cash/redwood/testing/TestRedwoodCompositionKt {
Expand Down
1 change: 1 addition & 0 deletions redwood-testing/api/redwood-testing.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ abstract interface <#A: kotlin/Any?> app.cash.redwood.testing/TestRedwoodComposi
abstract fun <get-uiConfigurations>(): kotlinx.coroutines.flow/MutableStateFlow<app.cash.redwood.ui/UiConfiguration> // app.cash.redwood.testing/TestRedwoodComposition.uiConfigurations.<get-uiConfigurations>|<get-uiConfigurations>(){}[0]

abstract fun saveState(): app.cash.redwood.testing/TestSavedState // app.cash.redwood.testing/TestRedwoodComposition.saveState|saveState(){}[0]
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]
abstract suspend fun awaitSnapshot(kotlin.time/Duration = ...): #A // app.cash.redwood.testing/TestRedwoodComposition.awaitSnapshot|awaitSnapshot(kotlin.time.Duration){}[0]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,16 @@ public fun <W : Any, S> TestRedwoodComposition(
}

public interface TestRedwoodComposition<S> : RedwoodComposition {
/**
* A fused call which does both [setContent] and [awaitSnapshot], but without sending a frame
* to the composition. The snapshot returned will always be the result of the synchronous
* recomposition of [content].
*/
public fun setContentAndSnapshot(content: @Composable () -> Unit): S

/**
* Returns a snapshot, waiting if necessary for changes to occur since the previous snapshot.
* Each call to this function is guaranteed to send at least once frame to the composition.
*
* @throws TimeoutCancellationException if no new snapshot is produced before [timeout].
*/
Expand Down Expand Up @@ -128,6 +136,13 @@ private class RealTestRedwoodComposition<W : Any, S>(
composition.setContent(content)
}

override fun setContentAndSnapshot(content: @Composable () -> Unit): S {
setContent(content)
check(hasChanges)
hasChanges = false
return createSnapshot()
}

override suspend fun awaitSnapshot(timeout: Duration): S {
check(contentSet) { "setContent must be called first!" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package app.cash.redwood.testing

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
Expand All @@ -28,12 +30,51 @@ import app.cash.redwood.widget.MutableListChildren
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.example.redwood.testapp.compose.Text
import com.example.redwood.testapp.testing.TestSchemaTester
import com.example.redwood.testapp.testing.TestSchemaTestingWidgetFactory
import com.example.redwood.testapp.testing.TextValue
import com.example.redwood.testapp.widget.TestSchemaWidgetSystem
import kotlin.test.Test
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest

class TestRedwoodCompositionTest {
@Test fun setContentAndSnapshot() = runTest {
TestSchemaTester {
var number by mutableIntStateOf(0)
val initial = setContentAndSnapshot {
Text("The number is: $number")
LaunchedEffect(Unit) {
number = 1
}
}

// Defer to allow effect to run.
delay(10.milliseconds)

assertThat(initial.single()).isEqualTo(TextValue(text = "The number is: 0"))
assertThat(awaitSnapshot().single()).isEqualTo(TextValue(text = "The number is: 1"))
}
}

@Test fun setContentThenAwaitSnapshot() = runTest {
TestSchemaTester {
var number by mutableIntStateOf(0)
setContent {
Text("The number is: $number")
LaunchedEffect(Unit) {
number = 1
}
}

// Defer to allow effect to run.
delay(10.milliseconds)

assertThat(awaitSnapshot().single()).isEqualTo(TextValue(text = "The number is: 1"))
}
}

@Test fun awaitSnapshotCapturesMultipleChanges() = runTest {
var count = 0
val tester = TestRedwoodComposition(
Expand Down

0 comments on commit 7a2bb8d

Please sign in to comment.