Skip to content

Commit

Permalink
Expose UI configuration state flow for testing (#1514)
Browse files Browse the repository at this point in the history
* Expose UI configuration state flow for testing

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

Co-authored-by: Veyndan Stuart <[email protected]>

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

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

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

---------

Co-authored-by: Veyndan Stuart <[email protected]>
  • Loading branch information
JakeWharton and veyndan authored Sep 27, 2023
1 parent 0bc7ce9 commit 6e98c9b
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutTestingWidgetFactory
import app.cash.redwood.protocol.widget.ProtocolBridge
import app.cash.redwood.testing.TestRedwoodComposition
import app.cash.redwood.testing.WidgetValue
import app.cash.redwood.ui.UiConfiguration
import app.cash.redwood.widget.MutableListChildren
import assertk.assertThat
import assertk.assertions.containsExactly
Expand All @@ -42,14 +41,13 @@ import com.example.redwood.testing.widget.TestSchemaWidgetFactories
import com.example.redwood.testing.widget.TestSchemaWidgetFactory
import kotlin.test.Test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest

class DirectChangeListenerTest : AbstractChangeListenerTest() {
override fun <T> CoroutineScope.launchComposition(
factories: TestSchemaWidgetFactories<WidgetValue>,
snapshot: () -> T,
) = TestRedwoodComposition(this, factories, MutableListChildren(), MutableStateFlow(UiConfiguration()), snapshot)
) = TestRedwoodComposition(this, factories, MutableListChildren(), createSnapshot = snapshot)
}

class ProtocolChangeListenerTest : AbstractChangeListenerTest() {
Expand All @@ -61,7 +59,7 @@ class ProtocolChangeListenerTest : AbstractChangeListenerTest() {
val widgetBridge = ProtocolBridge(MutableListChildren(), TestSchemaProtocolNodeFactory(factories)) {
throw AssertionError()
}
return TestRedwoodComposition(this, composeBridge.provider, composeBridge.root, MutableStateFlow(UiConfiguration())) {
return TestRedwoodComposition(this, composeBridge.provider, composeBridge.root) {
composeBridge.getChangesOrNull()?.let { changes ->
widgetBridge.sendChanges(changes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import app.cash.redwood.lazylayout.widget.WindowedLazyList
import app.cash.redwood.testing.TestRedwoodComposition
import app.cash.redwood.testing.WidgetValue
import app.cash.redwood.ui.Margin
import app.cash.redwood.ui.UiConfiguration
import app.cash.redwood.ui.dp
import app.cash.redwood.widget.MutableListChildren
import assertk.assertThat
Expand All @@ -40,7 +39,6 @@ import com.example.redwood.testing.widget.TestSchemaWidgetFactories
import com.example.redwood.testing.widget.TextValue
import kotlin.test.Test
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest

class LazyListTest {
Expand Down Expand Up @@ -148,7 +146,6 @@ private suspend fun <R> TestSchemaTester(
this,
factories,
container,
MutableStateFlow(UiConfiguration()),
) {
container.map { it.value }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ class ProtocolTest {
scope = backgroundScope,
provider = bridge.provider,
container = bridge.root,
uiConfigurations = MutableStateFlow(UiConfiguration()),
) {
bridge.getChangesOrNull() ?: emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package app.cash.redwood.testing
import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.Composable
import app.cash.redwood.compose.RedwoodComposition
import app.cash.redwood.compose.current
import app.cash.redwood.ui.Cancellable
import app.cash.redwood.ui.OnBackPressedCallback
import app.cash.redwood.ui.OnBackPressedDispatcher
Expand All @@ -30,7 +31,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withTimeout
Expand All @@ -42,10 +43,10 @@ public fun <W : Any, S> TestRedwoodComposition(
scope: CoroutineScope,
provider: Widget.Provider<W>,
container: Widget.Children<W>,
uiConfigurations: StateFlow<UiConfiguration>,
initialUiConfiguration: UiConfiguration = UiConfiguration(),
createSnapshot: () -> S,
): TestRedwoodComposition<S> {
return RealTestRedwoodComposition(scope, provider, container, uiConfigurations, createSnapshot)
return RealTestRedwoodComposition(scope, provider, container, initialUiConfiguration, createSnapshot)
}

public interface TestRedwoodComposition<S> : RedwoodComposition {
Expand All @@ -55,14 +56,20 @@ public interface TestRedwoodComposition<S> : RedwoodComposition {
* @throws TimeoutCancellationException if no new snapshot is produced before [timeout].
*/
public suspend fun awaitSnapshot(timeout: Duration = 1.seconds): S

/**
* The mutable [UiConfiguration] instance bound to [UiConfiguration.current][current]
* inside the composition.
*/
public val uiConfigurations: MutableStateFlow<UiConfiguration>
}

/** Performs Redwood composition strictly for testing. */
private class RealTestRedwoodComposition<W : Any, S>(
scope: CoroutineScope,
provider: Widget.Provider<W>,
container: Widget.Children<W>,
uiConfigurations: StateFlow<UiConfiguration>,
initialUiConfiguration: UiConfiguration,
createSnapshot: () -> S,
) : TestRedwoodComposition<S> {
/** Emit frames manually in [sendFrames]. */
Expand All @@ -73,6 +80,8 @@ private class RealTestRedwoodComposition<W : Any, S>(
/** Channel with the most recent snapshot, if any. */
private val snapshots = Channel<S>(Channel.CONFLATED)

override val uiConfigurations = MutableStateFlow(initialUiConfiguration)

private val composition = RedwoodComposition(
scope = scope + clock,
container = container,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package app.cash.redwood.testing
import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.Composable
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.compose.current
import app.cash.redwood.layout.widget.RedwoodLayoutTestingWidgetFactory
import app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutTestingWidgetFactory
import app.cash.redwood.protocol.Change
Expand All @@ -37,6 +38,7 @@ import app.cash.redwood.ui.OnBackPressedDispatcher
import app.cash.redwood.ui.UiConfiguration
import app.cash.redwood.widget.MutableListChildren
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.isEqualTo
import com.example.redwood.testing.compose.TestRow
import com.example.redwood.testing.compose.TestSchemaProtocolBridge
Expand All @@ -45,6 +47,7 @@ import com.example.redwood.testing.widget.TestSchemaProtocolNodeFactory
import com.example.redwood.testing.widget.TestSchemaTester
import com.example.redwood.testing.widget.TestSchemaTestingWidgetFactory
import com.example.redwood.testing.widget.TestSchemaWidgetFactories
import com.example.redwood.testing.widget.TextValue
import kotlin.test.Test
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.plus
Expand Down Expand Up @@ -142,4 +145,20 @@ class ViewTreesTest {

assertThat(widgetContainer.map { it.value }).isEqualTo(snapshot)
}

@Test fun uiConfigurationWorks() = runTest {
TestSchemaTester {
setContent {
Text("Dark: ${UiConfiguration.current.darkMode}")
}

val first = awaitSnapshot()
assertThat(first).containsExactly(TextValue(text = "Dark: false"))

uiConfigurations.value = UiConfiguration(darkMode = true)

val second = awaitSnapshot()
assertThat(second).containsExactly(TextValue(text = "Dark: true"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ suspend fun <R> ExampleTester(
RedwoodLayout = RedwoodLayoutTestingWidgetFactory(),
)
val container = MutableListChildren<WidgetValue>()
val tester = TestRedwoodComposition(this, factories, container, MutableStateFlow(UiConfiguration())) {
val tester = TestRedwoodComposition(this, factories, container) {
container.map { it.value }
}
try {
Expand Down Expand Up @@ -86,7 +86,7 @@ internal fun generateTester(schemaSet: SchemaSet): FileSpec {
}
.addCode("⇤)\n")
.addStatement("val container = %T<%T>()", RedwoodWidget.MutableListChildren, RedwoodTesting.WidgetValue)
.beginControlFlow("val tester = %T(this, factories, container, %M(%T()))", RedwoodTesting.TestRedwoodComposition, KotlinxCoroutines.MutableStateFlow, RedwoodRuntime.UiConfiguration)
.beginControlFlow("val tester = %T(this, factories, container)", RedwoodTesting.TestRedwoodComposition)
.addStatement("container.map { it.value }")
.endControlFlow()
.beginControlFlow("try")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ internal object Redwood {
.build()
}

internal object RedwoodRuntime {
val UiConfiguration = ClassName("app.cash.redwood.ui", "UiConfiguration")
}

internal object RedwoodTesting {
val TestRedwoodComposition = ClassName("app.cash.redwood.testing", "TestRedwoodComposition")
val WidgetValue = ClassName("app.cash.redwood.testing", "WidgetValue")
Expand Down Expand Up @@ -144,5 +140,4 @@ internal object KotlinxSerialization {

internal object KotlinxCoroutines {
val coroutineScope = MemberName("kotlinx.coroutines", "coroutineScope")
val MutableStateFlow = MemberName("kotlinx.coroutines.flow", "MutableStateFlow")
}

0 comments on commit 6e98c9b

Please sign in to comment.