@@ -9,6 +9,7 @@ import kotlinx.coroutines.CoroutineExceptionHandler
9
9
import kotlinx.coroutines.ExperimentalCoroutinesApi
10
10
import kotlinx.coroutines.cancel
11
11
import kotlinx.coroutines.channels.Channel
12
+ import kotlinx.coroutines.flow.MutableSharedFlow
12
13
import kotlinx.coroutines.flow.MutableStateFlow
13
14
import kotlinx.coroutines.flow.StateFlow
14
15
import kotlinx.coroutines.flow.map
@@ -21,6 +22,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher
21
22
import kotlinx.coroutines.test.TestScope
22
23
import kotlinx.coroutines.test.UnconfinedTestDispatcher
23
24
import kotlinx.coroutines.test.advanceUntilIdle
25
+ import kotlinx.coroutines.test.runCurrent
24
26
import kotlinx.coroutines.test.runTest
25
27
import okio.ByteString
26
28
import kotlin.test.Test
@@ -1180,6 +1182,249 @@ class RenderWorkflowInTest {
1180
1182
}
1181
1183
}
1182
1184
1185
+ @Test
1186
+ fun for_render_on_change_only_and_conflate_we_drain_action_but_do_not_render_no_state_changed () {
1187
+ runtimeTestRunner.runParametrizedTest(
1188
+ paramSource = runtimeOptions.filter {
1189
+ it.first.contains(RENDER_ONLY_WHEN_STATE_CHANGES ) && it.first.contains(
1190
+ CONFLATE_STALE_RENDERINGS
1191
+ )
1192
+ },
1193
+ before = ::setup,
1194
+ ) { (runtimeConfig: RuntimeConfig , workflowTracer: WorkflowTracer ? ) ->
1195
+ runTest(UnconfinedTestDispatcher ()) {
1196
+ check(runtimeConfig.contains(CONFLATE_STALE_RENDERINGS ))
1197
+ check(runtimeConfig.contains(RENDER_ONLY_WHEN_STATE_CHANGES ))
1198
+
1199
+ var renderCount = 0
1200
+ var childHandlerActionExecuted = 0
1201
+ var workerActionExecuted = 0
1202
+ val trigger = MutableSharedFlow <String >()
1203
+
1204
+ val childWorkflow = Workflow .stateful<String , String , String >(
1205
+ initialState = " unchanging state" ,
1206
+ render = { renderState ->
1207
+ runningWorker(
1208
+ trigger.asWorker()
1209
+ ) {
1210
+ action(" " ) {
1211
+ state = it
1212
+ setOutput(it)
1213
+ }
1214
+ }
1215
+ renderState
1216
+ }
1217
+ )
1218
+ val workflow = Workflow .stateful<String , String , String >(
1219
+ initialState = " unchanging state" ,
1220
+ render = { renderState ->
1221
+ renderChild(childWorkflow) { childOutput ->
1222
+ action(" childHandler" ) {
1223
+ childHandlerActionExecuted++
1224
+ state = childOutput
1225
+ }
1226
+ }
1227
+ runningWorker(
1228
+ trigger.asWorker()
1229
+ ) {
1230
+ action(" " ) {
1231
+ workerActionExecuted++
1232
+ state = it
1233
+ }
1234
+ }
1235
+ renderState.also {
1236
+ renderCount++
1237
+ }
1238
+ }
1239
+ )
1240
+ val props = MutableStateFlow (Unit )
1241
+ renderWorkflowIn(
1242
+ workflow = workflow,
1243
+ scope = backgroundScope,
1244
+ props = props,
1245
+ runtimeConfig = runtimeConfig,
1246
+ workflowTracer = workflowTracer,
1247
+ ) {}
1248
+
1249
+ launch {
1250
+ trigger.emit(" changed state" )
1251
+ }
1252
+ advanceUntilIdle()
1253
+
1254
+ // 2 renderings (initial and then the update.) Not *3* renderings.
1255
+ assertEquals(2 , renderCount)
1256
+ assertEquals(1 , childHandlerActionExecuted)
1257
+ assertEquals(1 , workerActionExecuted)
1258
+ }
1259
+ }
1260
+ }
1261
+
1262
+ @Test
1263
+ fun for_conflate_we_conflate_stacked_actions_into_one_rendering () {
1264
+ runtimeTestRunner.runParametrizedTest(
1265
+ paramSource = runtimeOptions
1266
+ .filter {
1267
+ it.first.contains(CONFLATE_STALE_RENDERINGS )
1268
+ },
1269
+ before = ::setup,
1270
+ ) { (runtimeConfig: RuntimeConfig , workflowTracer: WorkflowTracer ? ) ->
1271
+ runTest(StandardTestDispatcher ()) {
1272
+ check(runtimeConfig.contains(CONFLATE_STALE_RENDERINGS ))
1273
+
1274
+ var childHandlerActionExecuted = false
1275
+ val trigger = MutableSharedFlow <String >()
1276
+ val emitted = mutableListOf<String >()
1277
+
1278
+ val childWorkflow = Workflow .stateful<String , String , String >(
1279
+ initialState = " unchanging state" ,
1280
+ render = { renderState ->
1281
+ runningWorker(
1282
+ trigger.asWorker()
1283
+ ) {
1284
+ action(" " ) {
1285
+ state = it
1286
+ setOutput(it)
1287
+ }
1288
+ }
1289
+ renderState
1290
+ }
1291
+ )
1292
+ val workflow = Workflow .stateful<String , String , String >(
1293
+ initialState = " unchanging state" ,
1294
+ render = { renderState ->
1295
+ renderChild(childWorkflow) { childOutput ->
1296
+ action(" childHandler" ) {
1297
+ childHandlerActionExecuted = true
1298
+ state = childOutput
1299
+ }
1300
+ }
1301
+ runningWorker(
1302
+ trigger.asWorker()
1303
+ ) {
1304
+ action(" " ) {
1305
+ // Update the rendering in order to show conflation.
1306
+ state = " $it +update"
1307
+ }
1308
+ }
1309
+ renderState
1310
+ }
1311
+ )
1312
+ val props = MutableStateFlow (Unit )
1313
+ val renderings = renderWorkflowIn(
1314
+ workflow = workflow,
1315
+ scope = backgroundScope,
1316
+ props = props,
1317
+ runtimeConfig = runtimeConfig,
1318
+ workflowTracer = workflowTracer,
1319
+ ) {}
1320
+
1321
+ launch {
1322
+ trigger.emit(" changed state" )
1323
+ }
1324
+ val collectionJob = launch(UnconfinedTestDispatcher (testScheduler)) {
1325
+ // Collect this unconfined so we can get all the renderings faster than actions can
1326
+ // be processed.
1327
+ renderings.collect {
1328
+ emitted + = it.rendering
1329
+ }
1330
+ }
1331
+ advanceUntilIdle()
1332
+ runCurrent()
1333
+
1334
+ collectionJob.cancel()
1335
+
1336
+ // 2 renderings (initial and then the update.) Not *3* renderings.
1337
+ assertEquals(2 , emitted.size)
1338
+ assertEquals(" changed state+update" , emitted.last())
1339
+ assertTrue(childHandlerActionExecuted)
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ @Test
1345
+ fun for_conflate_we_do_not_conflate_stacked_actions_into_one_rendering_if_output () {
1346
+ runtimeTestRunner.runParametrizedTest(
1347
+ paramSource = runtimeOptions
1348
+ .filter {
1349
+ it.first.contains(CONFLATE_STALE_RENDERINGS )
1350
+ },
1351
+ before = ::setup,
1352
+ ) { (runtimeConfig: RuntimeConfig , workflowTracer: WorkflowTracer ? ) ->
1353
+ runTest(StandardTestDispatcher ()) {
1354
+ check(runtimeConfig.contains(CONFLATE_STALE_RENDERINGS ))
1355
+
1356
+ var childHandlerActionExecuted = false
1357
+ val trigger = MutableSharedFlow <String >()
1358
+ val emitted = mutableListOf<String >()
1359
+
1360
+ val childWorkflow = Workflow .stateful<String , String , String >(
1361
+ initialState = " unchanging state" ,
1362
+ render = { renderState ->
1363
+ runningWorker(
1364
+ trigger.asWorker()
1365
+ ) {
1366
+ action(" " ) {
1367
+ state = it
1368
+ setOutput(it)
1369
+ }
1370
+ }
1371
+ renderState
1372
+ }
1373
+ )
1374
+ val workflow = Workflow .stateful<String , String , String >(
1375
+ initialState = " unchanging state" ,
1376
+ render = { renderState ->
1377
+ renderChild(childWorkflow) { childOutput ->
1378
+ action(" childHandler" ) {
1379
+ childHandlerActionExecuted = true
1380
+ state = childOutput
1381
+ setOutput(childOutput)
1382
+ }
1383
+ }
1384
+ runningWorker(
1385
+ trigger.asWorker()
1386
+ ) {
1387
+ action(" " ) {
1388
+ // Update the rendering in order to show conflation.
1389
+ state = " $it +update"
1390
+ setOutput(" $it +update" )
1391
+ }
1392
+ }
1393
+ renderState
1394
+ }
1395
+ )
1396
+ val props = MutableStateFlow (Unit )
1397
+ val renderings = renderWorkflowIn(
1398
+ workflow = workflow,
1399
+ scope = backgroundScope,
1400
+ props = props,
1401
+ runtimeConfig = runtimeConfig,
1402
+ workflowTracer = workflowTracer,
1403
+ ) {}
1404
+
1405
+ launch {
1406
+ trigger.emit(" changed state" )
1407
+ }
1408
+ val collectionJob = launch(UnconfinedTestDispatcher (testScheduler)) {
1409
+ // Collect this unconfined so we can get all the renderings faster than actions can
1410
+ // be processed.
1411
+ renderings.collect {
1412
+ emitted + = it.rendering
1413
+ }
1414
+ }
1415
+ advanceUntilIdle()
1416
+ runCurrent()
1417
+
1418
+ collectionJob.cancel()
1419
+
1420
+ // 3 renderings because each had output.
1421
+ assertEquals(3 , emitted.size)
1422
+ assertEquals(" changed state+update" , emitted.last())
1423
+ assertTrue(childHandlerActionExecuted)
1424
+ }
1425
+ }
1426
+ }
1427
+
1183
1428
private class ExpectedException : RuntimeException ()
1184
1429
1185
1430
private fun <T1 , T2 > cartesianProduct (
0 commit comments