Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose UI configuration state flow for testing #1514

Merged
merged 6 commits into from
Sep 27, 2023
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 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I want to pipe the ability to specify the initial UiConfiguration instance through to this generated function. But I'll do that as a follow-up.

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")
}