@@ -26,6 +26,8 @@ import kotlinx.coroutines.channels.Channel
2626import kotlinx.coroutines.flow.MutableStateFlow
2727import kotlinx.coroutines.flow.collect
2828import kotlinx.coroutines.flow.consumeAsFlow
29+ import kotlinx.coroutines.flow.launchIn
30+ import kotlinx.coroutines.flow.onEach
2931import kotlinx.coroutines.isActive
3032import kotlinx.coroutines.launch
3133import kotlinx.coroutines.suspendCancellableCoroutine
@@ -363,5 +365,38 @@ class RenderWorkflowInTest {
363365 assertFalse(scope.isActive)
364366 }
365367
368+ @Test fun `output is emitted before next render pass` () {
369+ val outputTrigger = CompletableDeferred <String >()
370+ // A workflow whose state and rendering is the last output that it emitted.
371+ val workflow = Workflow .stateful<Unit , String , String , String >(
372+ initialState = { " {no output}" },
373+ render = { _, state ->
374+ runningWorker(Worker .from { outputTrigger.await() }) { output ->
375+ action {
376+ setOutput(output)
377+ nextState = output
378+ }
379+ }
380+ return @stateful state
381+ }
382+ )
383+ val scope = TestCoroutineScope ()
384+ val events = mutableListOf<String >()
385+ renderWorkflowIn(workflow, scope, MutableStateFlow (Unit )) { events + = " output($it )" }
386+ .onEach { events + = " rendering(${it.rendering} )" }
387+ .launchIn(scope)
388+ assertEquals(listOf (" rendering({no output})" ), events)
389+
390+ outputTrigger.complete(" output" )
391+ assertEquals(
392+ listOf (
393+ " rendering({no output})" ,
394+ " output(output)" ,
395+ " rendering(output)"
396+ ),
397+ events
398+ )
399+ }
400+
366401 private class ExpectedException : RuntimeException ()
367402}
0 commit comments