From 85328f5d5b94349902238935dff6436174039aaf Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sun, 28 Jun 2020 13:36:07 -0700 Subject: [PATCH 1/2] Add a passing test for the ordering of rendering and output emissions. --- .../squareup/workflow/RenderWorkflowInTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt index 1c402b498a..89c3bfda8f 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt @@ -26,6 +26,8 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine @@ -363,5 +365,38 @@ class RenderWorkflowInTest { assertFalse(scope.isActive) } + @Test fun `output is emitted before next render pass`() { + val outputTrigger = CompletableDeferred() + // A workflow whose state and rendering is the last output that it emitted. + val workflow = Workflow.stateful( + initialState = { "{no output}" }, + render = { _, state -> + runningWorker(Worker.from { outputTrigger.await() }) { output -> + action { + setOutput(output) + nextState = output + } + } + return@stateful state + } + ) + val scope = TestCoroutineScope() + val events = mutableListOf() + renderWorkflowIn(workflow, scope, MutableStateFlow(Unit)) { events += "output($it)" } + .onEach { events += "rendering(${it.rendering})" } + .launchIn(scope) + assertEquals(listOf("rendering({no output})"), events) + + outputTrigger.complete("output") + assertEquals( + listOf( + "rendering({no output})", + "rendering(output)", + "output(output)" + ), + events + ) + } + private class ExpectedException : RuntimeException() } From b084b16cf72618702e442e8ceac99cf168debbcd Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sun, 28 Jun 2020 09:12:59 -0700 Subject: [PATCH 2/2] Emit output before next render pass. Fixes #54. --- .../src/main/java/com/squareup/workflow/RenderWorkflow.kt | 6 ++---- .../test/java/com/squareup/workflow/RenderWorkflowInTest.kt | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt b/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt index 98425aef84..179dae4c51 100644 --- a/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt +++ b/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt @@ -158,12 +158,10 @@ fun renderWorkflowIn( // It might look weird to start by consuming the output before getting the rendering below, // but remember the first render pass already occurred above, before this coroutine was even // launched. - val output = runner.nextOutput() + runner.nextOutput() + ?.let { onOutput(it.value) } - // After receiving an output, the next render pass must be done before emitting that output, - // so that the workflow states appear consistent to observers of the outputs and renderings. renderingsAndSnapshots.value = runner.nextRendering() - output?.let { onOutput(it.value) } } } diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt index 89c3bfda8f..cb06bdd98d 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/RenderWorkflowInTest.kt @@ -391,8 +391,8 @@ class RenderWorkflowInTest { assertEquals( listOf( "rendering({no output})", - "rendering(output)", - "output(output)" + "output(output)", + "rendering(output)" ), events )