Skip to content

Commit 9dcac88

Browse files
1311: Add failing test for Main.immediate conflation
1 parent 9c6504f commit 9dcac88

File tree

7 files changed

+161
-0
lines changed

7 files changed

+161
-0
lines changed

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ include(
6464
":workflow-config:config-jvm",
6565
":workflow-core",
6666
":workflow-runtime",
67+
":workflow-runtime-android",
6768
":workflow-rx2",
6869
":workflow-testing",
6970
":workflow-tracing",

workflow-runtime-android/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Module Workflow Runtime Android
2+
3+
This module is an Android library that is used to test the Workflow Runtime with Android specific
4+
coroutine dispatchers. These are headless android-tests that run on device without UI.

workflow-runtime-android/api/workflow-runtime-android.api

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
plugins {
2+
id("com.android.library")
3+
id("kotlin-android")
4+
id("android-defaults")
5+
id("android-ui-tests")
6+
}
7+
8+
android {
9+
namespace = "com.squareup.workflow1"
10+
testNamespace = "$namespace.test"
11+
}
12+
13+
dependencies {
14+
api(project(":workflow-runtime"))
15+
implementation(project(":workflow-core"))
16+
17+
androidTestImplementation(libs.androidx.test.core)
18+
androidTestImplementation(libs.androidx.test.truth)
19+
androidTestImplementation(libs.kotlin.test.core)
20+
androidTestImplementation(libs.kotlin.test.jdk)
21+
androidTestImplementation(libs.kotlinx.coroutines.android)
22+
androidTestImplementation(libs.kotlinx.coroutines.core)
23+
androidTestImplementation(libs.kotlinx.coroutines.test)
24+
androidTestImplementation(libs.truth)
25+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
POM_ARTIFACT_ID=workflow-runtime-android
2+
POM_NAME=Workflow Runtime Android
3+
POM_PACKAGING=aar
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
3+
<application>
4+
<activity android:name="androidx.activity.ComponentActivity"/>
5+
</application>
6+
</manifest>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.squareup.workflow1
2+
3+
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
4+
import com.squareup.workflow1.WorkflowInterceptor.RenderPassesComplete
5+
import com.squareup.workflow1.WorkflowInterceptor.RuntimeLoopOutcome
6+
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.ExperimentalCoroutinesApi
9+
import kotlinx.coroutines.channels.Channel
10+
import kotlinx.coroutines.flow.MutableStateFlow
11+
import kotlinx.coroutines.flow.receiveAsFlow
12+
import kotlinx.coroutines.launch
13+
import kotlinx.coroutines.plus
14+
import kotlinx.coroutines.sync.Mutex
15+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
16+
import kotlinx.coroutines.test.runTest
17+
import org.junit.Ignore
18+
import org.junit.Test
19+
import kotlin.test.assertEquals
20+
import kotlin.test.assertTrue
21+
22+
@OptIn(WorkflowExperimentalRuntime::class, ExperimentalCoroutinesApi::class)
23+
class AndroidRenderWorkflowInTest {
24+
25+
@Ignore("#1311: Does not yet work with immediate dispatcher.")
26+
@Test
27+
fun with_conflate_we_conflate_stacked_actions_into_one_rendering() =
28+
runTest(UnconfinedTestDispatcher()) {
29+
30+
var childHandlerActionExecuted = false
31+
val trigger1 = Channel<String>(capacity = 1)
32+
val trigger2 = Channel<String>(capacity = 1)
33+
val emitted = mutableListOf<String>()
34+
var renderingsPassed = 0
35+
val countInterceptor = object : WorkflowInterceptor {
36+
override fun onRuntimeLoopTick(outcome: RuntimeLoopOutcome) {
37+
if (outcome is RenderPassesComplete<*>) {
38+
renderingsPassed++
39+
}
40+
}
41+
}
42+
43+
val childWorkflow = Workflow.stateful<String, String, String>(
44+
initialState = "unchanging state",
45+
render = { renderState ->
46+
runningWorker(
47+
trigger1.receiveAsFlow().asWorker()
48+
) {
49+
action("") {
50+
state = it
51+
setOutput(it)
52+
}
53+
}
54+
renderState
55+
}
56+
)
57+
val workflow = Workflow.stateful<String, String, String>(
58+
initialState = "unchanging state",
59+
render = { renderState ->
60+
renderChild(childWorkflow) { childOutput ->
61+
action("childHandler") {
62+
childHandlerActionExecuted = true
63+
state = childOutput
64+
}
65+
}
66+
runningWorker(
67+
trigger2.receiveAsFlow().asWorker()
68+
) {
69+
action("") {
70+
// Update the rendering in order to show conflation.
71+
state = "$it+update"
72+
setOutput(state)
73+
}
74+
}
75+
renderState
76+
}
77+
)
78+
val props = MutableStateFlow(Unit)
79+
// Render this on the Main.immediate dispatcher from Android.
80+
val renderings = renderWorkflowIn(
81+
workflow = workflow,
82+
scope = backgroundScope +
83+
Dispatchers.Main.immediate,
84+
props = props,
85+
runtimeConfig = setOf(CONFLATE_STALE_RENDERINGS),
86+
workflowTracer = null,
87+
interceptors = listOf(countInterceptor)
88+
) { }
89+
90+
val renderedMutex = Mutex(locked = true)
91+
92+
val collectionJob = launch(context = Dispatchers.Main.immediate, start = UNDISPATCHED) {
93+
// Collect this unconfined so we can get all the renderings faster than actions can
94+
// be processed.
95+
renderings.collect {
96+
emitted += it.rendering
97+
if (it.rendering == "changed state 2+update") {
98+
renderedMutex.unlock()
99+
}
100+
}
101+
}
102+
103+
launch(context = Dispatchers.Main.immediate, start = UNDISPATCHED) {
104+
trigger1.trySend("changed state 1")
105+
trigger2.trySend("changed state 2")
106+
}.join()
107+
108+
renderedMutex.lock()
109+
110+
collectionJob.cancel()
111+
112+
// 2 renderings (initial and then the update.) Not *3* renderings.
113+
assertEquals(2, emitted.size, "Expected only 2 renderings when conflating actions.")
114+
assertEquals(
115+
2,
116+
renderingsPassed,
117+
"Expected only 2 renderings passed when conflating actions."
118+
)
119+
assertEquals("changed state 2+update", emitted.last())
120+
assertTrue(childHandlerActionExecuted)
121+
}
122+
}

0 commit comments

Comments
 (0)