From f441c32a316eafca090e48782b761d48b9180a45 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sun, 28 Jun 2020 07:33:34 -0700 Subject: [PATCH] Rename EmittedOutput to WorkflowOutput, move to workflow-core. This simplifies the API of `WorkflowAction.applyTo`, removes the need for `OutputHolder` in the core tests and `MaybeOutput` in `workflow-runtime`. `EmittedOutput` is left as a deprecated typealias to ease migration. I was initially reluctant to do something like this because I didn't want Workflow to provide yet another generic `Optional` type. I forgot that the testing library was already doing that though, and I think that using a nullable "holder" type is a gross enough API that it won't likely be used outside of the intended uses. --- workflow-core/api/workflow-core.api | 10 ++- .../com/squareup/workflow/WorkflowAction.kt | 43 ++++++------ .../java/com/squareup/workflow/SinkTest.kt | 23 +++---- .../squareup/workflow/WorkflowActionTest.kt | 38 ++--------- .../com/squareup/workflow/RenderWorkflow.kt | 2 +- .../squareup/workflow/internal/MaybeOutput.kt | 41 ------------ .../workflow/internal/SubtreeManager.kt | 7 +- .../workflow/internal/WorkflowNode.kt | 13 ++-- .../workflow/internal/WorkflowRunner.kt | 5 +- .../internal/RealRenderContextTest.kt | 10 +-- .../workflow/internal/SubtreeManagerTest.kt | 32 ++++----- .../workflow/internal/WorkflowNodeTest.kt | 67 ++++++++++--------- .../workflow/internal/WorkflowRunnerTest.kt | 9 ++- workflow-testing/api/workflow-testing.api | 23 ++----- .../workflow/testing/RealRenderTester.kt | 33 ++++----- .../squareup/workflow/testing/RenderTester.kt | 23 ++++--- .../workflow/testing/RealRenderTesterTest.kt | 27 ++++---- .../tracing/TracingWorkflowInterceptor.kt | 12 ++-- 18 files changed, 172 insertions(+), 246 deletions(-) delete mode 100644 workflow-runtime/src/main/java/com/squareup/workflow/internal/MaybeOutput.kt diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index 2a2782ad95..055bc8e412 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -222,7 +222,7 @@ public final class com/squareup/workflow/WorkflowActionKt { public static final fun action (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/WorkflowAction; public static final fun action (Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/WorkflowAction; public static synthetic fun action$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow/WorkflowAction; - public static final fun applyTo (Lcom/squareup/workflow/WorkflowAction;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlin/Pair; + public static final fun applyTo (Lcom/squareup/workflow/WorkflowAction;Ljava/lang/Object;)Lkotlin/Pair; } public final class com/squareup/workflow/WorkflowIdentifier { @@ -245,3 +245,11 @@ public final class com/squareup/workflow/WorkflowKt { public static final fun mapRendering (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/Workflow; } +public final class com/squareup/workflow/WorkflowOutput { + public fun (Ljava/lang/Object;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/workflow-core/src/main/java/com/squareup/workflow/WorkflowAction.kt b/workflow-core/src/main/java/com/squareup/workflow/WorkflowAction.kt index 9aca2c2134..a6fcf48546 100644 --- a/workflow-core/src/main/java/com/squareup/workflow/WorkflowAction.kt +++ b/workflow-core/src/main/java/com/squareup/workflow/WorkflowAction.kt @@ -32,21 +32,16 @@ interface WorkflowAction { * @param nextState the state that the workflow should move to. Default is the current state. */ class Updater(var nextState: S) { - private var output: @UnsafeVariance O? = null - private var isOutputSet = false + internal var output: WorkflowOutput<@UnsafeVariance O>? = null + private set /** * Sets the value the workflow will emit as output when this action is applied. * If this method is not called, there will be no output. */ fun setOutput(output: O) { - this.output = output - isOutputSet = true + this.output = WorkflowOutput(output) } - - @Suppress("UNCHECKED_CAST") - internal fun mapOutput(mapper: (@UnsafeVariance O) -> T): T? = - if (isOutputSet) mapper(output as O) else null } /** @@ -211,19 +206,25 @@ inline fun action( override fun toString(): String = "WorkflowAction(${name()})@${hashCode()}" } -/** - * Applies this [WorkflowAction] to [state]. If the action sets an output, the output will be - * transformed by [mapOutput], and then both the new state and the transformed output will be - * returned. - * - * If the action sets the output multiple times, only the last one will be used. - */ -fun WorkflowAction.applyTo( - state: StateT, - mapOutput: (OutputT) -> T -): Pair { +/** Applies this [WorkflowAction] to [state]. */ +@ExperimentalWorkflowApi +fun WorkflowAction.applyTo( + state: StateT +): Pair?> { val updater = Updater(state) updater.apply() - val output = updater.mapOutput(mapOutput) - return Pair(updater.nextState, output) + return Pair(updater.nextState, updater.output) +} + +/** Wrapper around a potentially-nullable [OutputT] value. */ +class WorkflowOutput(val value: OutputT) { + override fun toString(): String = "WorkflowOutput($value)" + + override fun equals(other: Any?): Boolean = when { + this === other -> true + other !is WorkflowOutput<*> -> false + else -> value == other.value + } + + override fun hashCode(): Int = value.hashCode() } diff --git a/workflow-core/src/test/java/com/squareup/workflow/SinkTest.kt b/workflow-core/src/test/java/com/squareup/workflow/SinkTest.kt index 55c04ea2fb..7bfc689125 100644 --- a/workflow-core/src/test/java/com/squareup/workflow/SinkTest.kt +++ b/workflow-core/src/test/java/com/squareup/workflow/SinkTest.kt @@ -22,7 +22,6 @@ import kotlinx.coroutines.test.runBlockingTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse -import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.test.fail @@ -52,11 +51,9 @@ class SinkTest { assertEquals(1, sink.actions.size) sink.actions.removeFirst() .let { action -> - val (newState, output) = action.applyTo("state") { - assertEquals("output: 1", it) - } + val (newState, output) = action.applyTo("state") assertEquals("state 1", newState) - assertNotNull(output) + assertEquals("output: 1", output?.value) } assertTrue(sink.actions.isEmpty()) @@ -65,11 +62,9 @@ class SinkTest { assertEquals(1, sink.actions.size) sink.actions.removeFirst() .let { action -> - val (newState, output) = action.applyTo("state") { - assertEquals("output: 2", it) - } + val (newState, output) = action.applyTo("state") assertEquals("state 2", newState) - assertNotNull(output) + assertEquals("output: 2", output?.value) } collector.cancel() @@ -89,12 +84,10 @@ class SinkTest { advanceUntilIdle() val enqueuedAction = sink.actions.removeFirst() - val (newState, output) = enqueuedAction.applyTo("state") { - assertEquals("output", it) - } + val (newState, output) = enqueuedAction.applyTo("state") assertEquals(1, applications) assertEquals("state applied", newState) - assertNotNull(output) + assertEquals("output", output?.value) } } @@ -114,7 +107,7 @@ class SinkTest { val enqueuedAction = sink.actions.removeFirst() pauseDispatcher() - enqueuedAction.applyTo("state") {} + enqueuedAction.applyTo("state") assertFalse(resumed) resumeDispatcher() @@ -138,7 +131,7 @@ class SinkTest { val enqueuedAction = sink.actions.removeFirst() sendJob.cancel() advanceUntilIdle() - val (newState, output) = enqueuedAction.applyTo("ignored") {} + val (newState, output) = enqueuedAction.applyTo("ignored") assertFalse(applied) assertEquals("ignored", newState) diff --git a/workflow-core/src/test/java/com/squareup/workflow/WorkflowActionTest.kt b/workflow-core/src/test/java/com/squareup/workflow/WorkflowActionTest.kt index 10e7deac44..abb959cc2e 100644 --- a/workflow-core/src/test/java/com/squareup/workflow/WorkflowActionTest.kt +++ b/workflow-core/src/test/java/com/squareup/workflow/WorkflowActionTest.kt @@ -21,6 +21,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +@OptIn(ExperimentalWorkflowApi::class) class WorkflowActionTest { @Test fun `applyTo works when no output is set`() { @@ -29,7 +30,7 @@ class WorkflowActionTest { nextState = "nextState: $nextState" } } - val (nextState, output) = action.applyTo("state", ::OutputHolder) + val (nextState, output) = action.applyTo("state") assertEquals("nextState: state", nextState) assertNull(output) } @@ -41,7 +42,7 @@ class WorkflowActionTest { setOutput(null) } } - val (nextState, output) = action.applyTo("state", ::OutputHolder) + val (nextState, output) = action.applyTo("state") assertEquals("nextState: state", nextState) assertNotNull(output) assertNull(output.value) @@ -54,40 +55,9 @@ class WorkflowActionTest { setOutput("output") } } - val (nextState, output) = action.applyTo("state", ::OutputHolder) + val (nextState, output) = action.applyTo("state") assertEquals("nextState: state", nextState) assertNotNull(output) assertEquals("output", output.value) } - - @Test fun `applyTo doens't invoke mapOutput when output is not set`() { - val action = object : WorkflowAction { - override fun Updater.apply() { - nextState = "nextState: $nextState" - } - } - var outputCalls = 0 - val (nextState, output) = action.applyTo("state") { outputCalls++ } - assertEquals("nextState: state", nextState) - assertNull(output) - assertEquals(0, outputCalls) - } - - @Test fun `applyTo only invokes mapOutput once when output is set multiple times`() { - val action = object : WorkflowAction { - override fun Updater.apply() { - setOutput("first output") - nextState = "nextState: $nextState" - setOutput(null) - setOutput("third output") - } - } - val outputs = mutableListOf() - val (nextState, output) = action.applyTo("state") { outputs += it } - assertEquals("nextState: state", nextState) - assertNotNull(output) - assertEquals(listOf("third output"), outputs) - } - - private data class OutputHolder(val value: O) } 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 c3e8c88793..98425aef84 100644 --- a/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt +++ b/workflow-runtime/src/main/java/com/squareup/workflow/RenderWorkflow.kt @@ -163,7 +163,7 @@ fun renderWorkflowIn( // 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.withValue { onOutput(it) } + output?.let { onOutput(it.value) } } } diff --git a/workflow-runtime/src/main/java/com/squareup/workflow/internal/MaybeOutput.kt b/workflow-runtime/src/main/java/com/squareup/workflow/internal/MaybeOutput.kt deleted file mode 100644 index 58345259aa..0000000000 --- a/workflow-runtime/src/main/java/com/squareup/workflow/internal/MaybeOutput.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.squareup.workflow.internal - -/** - * Simple Optional type to hold outputs. Create with either [NONE] or [of]. - */ -@Suppress("UNCHECKED_CAST") -internal class MaybeOutput constructor(private val value: Any?) { - - inline val hasValue: Boolean get() = value !== NO_VALUE - - fun getValueOrThrow(): O { - if (value === NO_VALUE) throw NoSuchElementException() - return value as O - } - - inline fun withValue(block: (O) -> Unit) { - if (value !== NO_VALUE) block(value as O) - } - - companion object { - private val NO_VALUE = Any() - - fun none(): MaybeOutput = MaybeOutput(NO_VALUE) - fun of(value: O) = MaybeOutput(value) - } -} diff --git a/workflow-runtime/src/main/java/com/squareup/workflow/internal/SubtreeManager.kt b/workflow-runtime/src/main/java/com/squareup/workflow/internal/SubtreeManager.kt index 025707dd30..9ffed1be63 100644 --- a/workflow-runtime/src/main/java/com/squareup/workflow/internal/SubtreeManager.kt +++ b/workflow-runtime/src/main/java/com/squareup/workflow/internal/SubtreeManager.kt @@ -22,6 +22,7 @@ import com.squareup.workflow.Workflow import com.squareup.workflow.WorkflowAction import com.squareup.workflow.WorkflowInterceptor import com.squareup.workflow.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow.WorkflowOutput import kotlinx.coroutines.selects.SelectBuilder import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -98,7 +99,7 @@ import kotlin.coroutines.EmptyCoroutineContext internal class SubtreeManager( snapshotCache: Map, private val contextForChildren: CoroutineContext, - private val emitActionToParent: (WorkflowAction) -> MaybeOutput, + private val emitActionToParent: (WorkflowAction) -> WorkflowOutput?, private val workflowSession: WorkflowSession? = null, private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, private val idCounter: IdCounter? = null, @@ -158,7 +159,7 @@ internal class SubtreeManager( * Uses [selector] to invoke [WorkflowNode.tick] for every running child workflow this instance * is managing. */ - fun tickChildren(selector: SelectBuilder>) { + fun tickChildren(selector: SelectBuilder?>) { children.forEachActive { child -> child.workflowNode.tick(selector) } @@ -182,7 +183,7 @@ internal class SubtreeManager( val id = child.id(key) lateinit var node: WorkflowChildNode - fun acceptChildOutput(output: ChildOutputT): MaybeOutput { + fun acceptChildOutput(output: ChildOutputT): WorkflowOutput? { val action = node.acceptChildOutput(output) return emitActionToParent(action) } diff --git a/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowNode.kt b/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowNode.kt index bb911609de..5632919b8f 100644 --- a/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowNode.kt +++ b/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowNode.kt @@ -25,6 +25,7 @@ import com.squareup.workflow.WorkflowAction import com.squareup.workflow.WorkflowIdentifier import com.squareup.workflow.WorkflowInterceptor import com.squareup.workflow.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.applyTo import com.squareup.workflow.intercept import com.squareup.workflow.internal.RealRenderContext.SideEffectRunner @@ -60,7 +61,7 @@ internal class WorkflowNode( initialProps: PropsT, snapshot: TreeSnapshot, baseContext: CoroutineContext, - private val emitOutputToParent: (OutputT) -> MaybeOutput = { MaybeOutput.of(it) }, + private val emitOutputToParent: (OutputT) -> WorkflowOutput? = { WorkflowOutput(it) }, override val parent: WorkflowSession? = null, private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, idCounter: IdCounter? = null, @@ -183,7 +184,7 @@ internal class WorkflowNode( * * It is an error to call this method after calling [cancel]. */ - fun tick(selector: SelectBuilder>) { + fun tick(selector: SelectBuilder?>) { // Listen for any child workflow updates. subtreeManager.tickChildren(selector) @@ -198,7 +199,7 @@ internal class WorkflowNode( // Set the tombstone flag so we don't continue to listen to the subscription. child.tombstone = true // Nothing to do on close other than update the session, so don't emit any output. - return@onReceive MaybeOutput.none() + return@onReceive null } else { val update = child.acceptUpdate(valueOrDone.value) @Suppress("UNCHECKED_CAST") @@ -277,11 +278,11 @@ internal class WorkflowNode( * Applies [action] to this workflow's [state] and * [emits an output to its parent][emitOutputToParent] if necessary. */ - private fun applyAction(action: WorkflowAction): MaybeOutput { - val (newState, tickResult) = action.applyTo(state, emitOutputToParent) + private fun applyAction(action: WorkflowAction): WorkflowOutput? { + val (newState, tickResult) = action.applyTo(state) state = newState @Suppress("UNCHECKED_CAST") - return (tickResult ?: MaybeOutput.none()) as MaybeOutput + return tickResult?.let { emitOutputToParent(it.value) } as WorkflowOutput? } private fun createWorkerNode( diff --git a/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowRunner.kt b/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowRunner.kt index 4237e84856..4d406496ff 100644 --- a/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowRunner.kt +++ b/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowRunner.kt @@ -20,6 +20,7 @@ import com.squareup.workflow.RenderingAndSnapshot import com.squareup.workflow.TreeSnapshot import com.squareup.workflow.Workflow import com.squareup.workflow.WorkflowInterceptor +import com.squareup.workflow.WorkflowOutput import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -83,7 +84,7 @@ internal class WorkflowRunner( // Tick _might_ return an output, but if it returns null, it means the state or a child // probably changed, so we should re-render/snapshot and emit again. - suspend fun nextOutput(): MaybeOutput = select { + suspend fun nextOutput(): WorkflowOutput? = select { // Stop trying to read from the inputs channel after it's closed. if (!propsChannel.isClosedForReceive) { // TODO(https://github.com/square/workflow/issues/512) Replace with receiveOrClosed. @@ -95,7 +96,7 @@ internal class WorkflowRunner( } } // Return null to tell the caller to do another render pass, but not emit an output. - return@onReceiveOrNull MaybeOutput.none() + return@onReceiveOrNull null } } diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/internal/RealRenderContextTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/internal/RealRenderContextTest.kt index 83c1bb4490..4e6e63e1e7 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/internal/RealRenderContextTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/internal/RealRenderContextTest.kt @@ -17,6 +17,7 @@ package com.squareup.workflow.internal +import com.squareup.workflow.ExperimentalWorkflowApi import com.squareup.workflow.RenderContext import com.squareup.workflow.Sink import com.squareup.workflow.Snapshot @@ -45,6 +46,7 @@ import kotlin.test.assertSame import kotlin.test.assertTrue import kotlin.test.fail +@OptIn(ExperimentalWorkflowApi::class) class RealRenderContextTest { private class TestRenderer : Renderer { @@ -224,9 +226,9 @@ class RealRenderContextTest { sink.send("foo") val update = eventActionsChannel.poll()!! - val (state, output) = update.applyTo("state") { MaybeOutput.of(it) } + val (state, output) = update.applyTo("state") assertEquals("state", state) - assertEquals("foo", output?.getValueOrThrow()) + assertEquals("foo", output?.value) } @Test fun `renderChild works`() { @@ -242,9 +244,9 @@ class RealRenderContextTest { assertEquals("key", key) val (state, output) = handler.invoke("output") - .applyTo("state") { MaybeOutput.of(it) } + .applyTo("state") assertEquals("state", state) - assertEquals("output:output", output?.getValueOrThrow()) + assertEquals("output:output", output?.value) } @Test fun `all methods throw after freeze`() { diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/internal/SubtreeManagerTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/internal/SubtreeManagerTest.kt index 8a262190b0..d8f478a84b 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/internal/SubtreeManagerTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/internal/SubtreeManagerTest.kt @@ -17,11 +17,13 @@ package com.squareup.workflow.internal +import com.squareup.workflow.ExperimentalWorkflowApi import com.squareup.workflow.RenderContext import com.squareup.workflow.Sink import com.squareup.workflow.Snapshot import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.WorkflowAction +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.action import com.squareup.workflow.applyTo import com.squareup.workflow.internal.SubtreeManagerTest.TestWorkflow.Rendering @@ -38,6 +40,7 @@ import kotlin.test.fail private typealias StringHandler = (String) -> WorkflowAction +@OptIn(ExperimentalWorkflowApi::class) class SubtreeManagerTest { private class TestWorkflow : StatefulWorkflow() { @@ -176,11 +179,10 @@ class SubtreeManagerTest { assertFalse(tickOutput.isCompleted) eventHandler("event!") - val update = tickOutput.await() - .getValueOrThrow() + val update = tickOutput.await()!!.value - val (_, output) = update.applyTo("state") { MaybeOutput.of(it) } - assertEquals("case output:workflow output:event!", output?.getValueOrThrow()) + val (_, output) = update.applyTo("state") + assertEquals("case output:workflow output:event!", output?.value) } } @@ -196,24 +198,18 @@ class SubtreeManagerTest { render { action { setOutput("initial handler: $it") } } .let { rendering -> rendering.eventHandler("initial output") - val initialAction = manager.tickAction() - .getValueOrThrow() - val (_, initialOutput) = initialAction.applyTo("") { MaybeOutput.of(it) } - assertEquals( - "initial handler: workflow output:initial output", initialOutput?.getValueOrThrow() - ) + val initialAction = manager.tickAction()!!.value + val (_, initialOutput) = initialAction.applyTo("") + assertEquals("initial handler: workflow output:initial output", initialOutput?.value) } // Do a second render + tick, but with a different handler function. render { action { setOutput("second handler: $it") } } .let { rendering -> rendering.eventHandler("second output") - val secondAction = manager.tickAction() - .getValueOrThrow() - val (_, secondOutput) = secondAction.applyTo("") { MaybeOutput.of(it) } - assertEquals( - "second handler: workflow output:second output", secondOutput?.getValueOrThrow() - ) + val secondAction = manager.tickAction()!!.value + val (_, secondOutput) = secondAction.applyTo("") + assertEquals("second handler: workflow output:second output", secondOutput?.value) } } } @@ -249,8 +245,8 @@ class SubtreeManagerTest { } private suspend fun SubtreeManager.tickAction() = - select>> { tickChildren(this) } + select>?> { tickChildren(this) } private fun subtreeManagerForTest() = - SubtreeManager(emptyMap(), context, emitActionToParent = { MaybeOutput.of(it) }) + SubtreeManager(emptyMap(), context, emitActionToParent = { WorkflowOutput(it) }) } diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowNodeTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowNodeTest.kt index d60948704f..31e27faa94 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowNodeTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowNodeTest.kt @@ -31,6 +31,7 @@ import com.squareup.workflow.WorkflowAction.Updater import com.squareup.workflow.WorkflowIdentifier import com.squareup.workflow.WorkflowInterceptor import com.squareup.workflow.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.action import com.squareup.workflow.asWorker import com.squareup.workflow.contraMap @@ -189,7 +190,7 @@ class WorkflowNodeTest { } val node = WorkflowNode( workflow.id(), workflow, "", TreeSnapshot.NONE, context, - emitOutputToParent = { MaybeOutput.of("tick:$it") } + emitOutputToParent = { WorkflowOutput("tick:$it") } ) node.render(workflow, "") @@ -197,12 +198,12 @@ class WorkflowNodeTest { val result = runBlocking { withTimeout(10) { - select> { + select?> { node.tick(this) } } } - assertEquals("tick:event", result.getValueOrThrow()) + assertEquals("tick:event", result?.value) } @Test fun `accepts events sent to stale renderings`() { @@ -226,7 +227,7 @@ class WorkflowNodeTest { } } val node = WorkflowNode(workflow.id(), workflow, "", TreeSnapshot.NONE, context, - emitOutputToParent = { MaybeOutput.of("tick:$it") } + emitOutputToParent = { WorkflowOutput("tick:$it") } ) node.render(workflow, "") @@ -236,13 +237,13 @@ class WorkflowNodeTest { val result = runBlocking { withTimeout(10) { List(2) { - select> { + select?> { node.tick(this) } } } } - assertEquals(listOf("tick:event", "tick:event2"), result.map { it.getValueOrThrow() }) + assertEquals(listOf("tick:event", "tick:event2"), result.map { it?.value }) } @Test fun `send allows subsequent events on same rendering`() { @@ -338,7 +339,7 @@ class WorkflowNodeTest { val output = runBlocking { try { withTimeout(1) { - select> { + select?> { node.tick(this) } } @@ -350,14 +351,14 @@ class WorkflowNodeTest { channel.send("element") withTimeout(1) { - select> { + select?> { node.tick(this) } } } assertEquals("element", update) - assertEquals("update:element", output.getValueOrThrow()) + assertEquals("update:element", output?.value) } @Test fun `worker is cancelled`() { @@ -405,7 +406,7 @@ class WorkflowNodeTest { // This tick will process the event handler, it won't close the channel yet. withTimeout(1) { - select> { + select?> { node.tick(this) } } @@ -524,13 +525,13 @@ class WorkflowNodeTest { val result = runBlocking { // Result should be available instantly, any delay at all indicates something is broken. withTimeout(1) { - select> { + select?> { node.tick(this) } } } - assertEquals("result", result.getValueOrThrow()) + assertEquals("result", result?.value) } @Test fun `sideEffect is cancelled when stops being ran`() { @@ -1279,7 +1280,7 @@ class WorkflowNodeTest { sink.send("hello") runBlocking { - select> { + select?> { node.tick(this) } } @@ -1298,19 +1299,19 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of("output:$it") } + emitOutputToParent = { WorkflowOutput("output:$it") } ) val rendering = node.render(workflow.asStatefulWorkflow(), Unit) rendering.send("hello") val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertEquals("output:hello", output.getValueOrThrow()) + assertEquals("output:hello", output?.value) } @Test fun `actionSink action allows null output`() { @@ -1323,19 +1324,19 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of(it) } + emitOutputToParent = { WorkflowOutput(it) } ) val rendering = node.render(workflow.asStatefulWorkflow(), Unit) rendering.send("hello") val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertNull(output.getValueOrThrow()) + assertNull(output?.value) } @Test fun `worker action changes state`() { @@ -1356,7 +1357,7 @@ class WorkflowNodeTest { node.render(workflow.asStatefulWorkflow(), Unit) runBlocking { - select> { + select?> { node.tick(this) } } @@ -1374,17 +1375,17 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of("output:$it") } + emitOutputToParent = { WorkflowOutput("output:$it") } ) node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertEquals("output:hello", output.getValueOrThrow()) + assertEquals("output:hello", output?.value) } @Test fun `worker action allows null output`() { @@ -1396,17 +1397,17 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of(it) } + emitOutputToParent = { WorkflowOutput(it) } ) node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertNull(output.getValueOrThrow()) + assertNull(output?.value) } @Test fun `child action changes state`() { @@ -1429,7 +1430,7 @@ class WorkflowNodeTest { node.render(workflow.asStatefulWorkflow(), Unit) runBlocking { - select> { + select?> { node.tick(this) } } @@ -1450,17 +1451,17 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of("output:$it") } + emitOutputToParent = { WorkflowOutput("output:$it") } ) node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertEquals("output:child:hello", output.getValueOrThrow()) + assertEquals("output:child:hello", output?.value) } @Test fun `child action allows null output`() { @@ -1475,17 +1476,17 @@ class WorkflowNodeTest { initialProps = Unit, snapshot = TreeSnapshot.NONE, baseContext = Unconfined, - emitOutputToParent = { MaybeOutput.of(it) } + emitOutputToParent = { WorkflowOutput(it) } ) node.render(workflow.asStatefulWorkflow(), Unit) val output = runBlocking { - select> { + select?> { node.tick(this) } } - assertNull(output.getValueOrThrow()) + assertNull(output?.value) } private class TestSession(override val sessionId: Long = 0) : WorkflowSession { diff --git a/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowRunnerTest.kt b/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowRunnerTest.kt index 3e6a536ab9..9d435beb0f 100644 --- a/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowRunnerTest.kt +++ b/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowRunnerTest.kt @@ -37,7 +37,6 @@ import org.junit.Test import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.assertEquals -import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -101,7 +100,7 @@ class WorkflowRunnerTest { dispatcher.resumeDispatcher() assertTrue(output.isCompleted) - assertFalse(output.getCompleted().hasValue) + assertNull(output.getCompleted()) val rendering = runner.nextRendering().rendering assertEquals("changed", rendering) } @@ -127,7 +126,7 @@ class WorkflowRunnerTest { val output = scope.async { runner.nextOutput() } .getCompleted() - assertEquals("output: work", output.getValueOrThrow()) + assertEquals("output: work", output?.value) val updatedRendering = runner.nextRendering().rendering assertEquals("state: work", updatedRendering) @@ -159,13 +158,13 @@ class WorkflowRunnerTest { val firstOutput = scope.async { runner.nextOutput() } .getCompleted() // First update will be props, so no output value. - assertFalse(firstOutput.hasValue) + assertNull(firstOutput) val secondRendering = runner.nextRendering().rendering assertEquals("changed props|initial state(initial props)", secondRendering) val secondOutput = scope.async { runner.nextOutput() } .getCompleted() - assertEquals("output: work", secondOutput.getValueOrThrow()) + assertEquals("output: work", secondOutput?.value) val thirdRendering = runner.nextRendering().rendering assertEquals("changed props|state: work", thirdRendering) } diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index 4bb8d06c1f..007490b9d3 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -1,14 +1,3 @@ -public final class com/squareup/workflow/testing/EmittedOutput { - public fun (Ljava/lang/Object;)V - public final fun component1 ()Ljava/lang/Object; - public final fun copy (Ljava/lang/Object;)Lcom/squareup/workflow/testing/EmittedOutput; - public static synthetic fun copy$default (Lcom/squareup/workflow/testing/EmittedOutput;Ljava/lang/Object;ILjava/lang/Object;)Lcom/squareup/workflow/testing/EmittedOutput; - public fun equals (Ljava/lang/Object;)Z - public final fun getOutput ()Ljava/lang/Object; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - public final class com/squareup/workflow/testing/RenderIdempotencyChecker : com/squareup/workflow/WorkflowInterceptor { public static final field INSTANCE Lcom/squareup/workflow/testing/RenderIdempotencyChecker; public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow/Snapshot;Lkotlin/jvm/functions/Function2;Lcom/squareup/workflow/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; @@ -32,20 +21,20 @@ public final class com/squareup/workflow/testing/RenderTestResult$DefaultImpls { public abstract interface class com/squareup/workflow/testing/RenderTester { public abstract fun expectSideEffect (Ljava/lang/String;)Lcom/squareup/workflow/testing/RenderTester; - public abstract fun expectWorker (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lcom/squareup/workflow/testing/EmittedOutput;)Lcom/squareup/workflow/testing/RenderTester; - public abstract fun expectWorkflow (Lkotlin/reflect/KClass;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/testing/EmittedOutput;)Lcom/squareup/workflow/testing/RenderTester; + public abstract fun expectWorker (Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lcom/squareup/workflow/WorkflowOutput;)Lcom/squareup/workflow/testing/RenderTester; + public abstract fun expectWorkflow (Lkotlin/reflect/KClass;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/WorkflowOutput;)Lcom/squareup/workflow/testing/RenderTester; public abstract fun render (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow/testing/RenderTestResult; } public final class com/squareup/workflow/testing/RenderTester$DefaultImpls { - public static synthetic fun expectWorker$default (Lcom/squareup/workflow/testing/RenderTester;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lcom/squareup/workflow/testing/EmittedOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; - public static synthetic fun expectWorkflow$default (Lcom/squareup/workflow/testing/RenderTester;Lkotlin/reflect/KClass;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/testing/EmittedOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; + public static synthetic fun expectWorker$default (Lcom/squareup/workflow/testing/RenderTester;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lcom/squareup/workflow/WorkflowOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; + public static synthetic fun expectWorkflow$default (Lcom/squareup/workflow/testing/RenderTester;Lkotlin/reflect/KClass;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/WorkflowOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; public static synthetic fun render$default (Lcom/squareup/workflow/testing/RenderTester;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTestResult; } public final class com/squareup/workflow/testing/RenderTesterKt { - public static final fun expectWorker (Lcom/squareup/workflow/testing/RenderTester;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lcom/squareup/workflow/testing/EmittedOutput;)Lcom/squareup/workflow/testing/RenderTester; - public static synthetic fun expectWorker$default (Lcom/squareup/workflow/testing/RenderTester;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lcom/squareup/workflow/testing/EmittedOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; + public static final fun expectWorker (Lcom/squareup/workflow/testing/RenderTester;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lcom/squareup/workflow/WorkflowOutput;)Lcom/squareup/workflow/testing/RenderTester; + public static synthetic fun expectWorker$default (Lcom/squareup/workflow/testing/RenderTester;Lcom/squareup/workflow/Worker;Ljava/lang/String;Lcom/squareup/workflow/WorkflowOutput;ILjava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; public static final fun renderTester (Lcom/squareup/workflow/StatefulWorkflow;Ljava/lang/Object;Ljava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; public static final fun renderTester (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;)Lcom/squareup/workflow/testing/RenderTester; } diff --git a/workflow-testing/src/main/java/com/squareup/workflow/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow/testing/RealRenderTester.kt index 4decf43573..2971a0af91 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow/testing/RealRenderTester.kt @@ -15,6 +15,7 @@ */ package com.squareup.workflow.testing +import com.squareup.workflow.ExperimentalWorkflowApi import com.squareup.workflow.RenderContext import com.squareup.workflow.Sink import com.squareup.workflow.StatefulWorkflow @@ -22,12 +23,14 @@ import com.squareup.workflow.Worker import com.squareup.workflow.Workflow import com.squareup.workflow.WorkflowAction import com.squareup.workflow.WorkflowAction.Companion.noAction +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.applyTo import com.squareup.workflow.testing.RealRenderTester.Expectation.ExpectedSideEffect import com.squareup.workflow.testing.RealRenderTester.Expectation.ExpectedWorker import com.squareup.workflow.testing.RealRenderTester.Expectation.ExpectedWorkflow import kotlin.reflect.KClass +@OptIn(ExperimentalWorkflowApi::class) internal class RealRenderTester( private val workflow: StatefulWorkflow, private val props: PropsT, @@ -42,20 +45,20 @@ internal class RealRenderTester( Sink> { internal sealed class Expectation { - open val output: EmittedOutput? = null + open val output: WorkflowOutput? = null data class ExpectedWorkflow( val workflowType: KClass>, val key: String, val assertProps: (props: Any?) -> Unit, val rendering: RenderingT, - override val output: EmittedOutput? + override val output: WorkflowOutput? ) : Expectation() data class ExpectedWorker( val matchesWhen: (otherWorker: Worker<*>) -> Boolean, val key: String, - override val output: EmittedOutput? + override val output: WorkflowOutput? ) : Expectation() data class ExpectedSideEffect(val key: String) : Expectation() @@ -68,7 +71,7 @@ internal class RealRenderTester( rendering: ChildRenderingT, key: String, assertProps: (props: ChildPropsT) -> Unit, - output: EmittedOutput? + output: WorkflowOutput? ): RenderTester { @Suppress("UNCHECKED_CAST") val assertAnyProps = { props: Any? -> assertProps(props as ChildPropsT) } @@ -84,7 +87,7 @@ internal class RealRenderTester( override fun expectWorker( matchesWhen: (otherWorker: Worker<*>) -> Boolean, key: String, - output: EmittedOutput? + output: WorkflowOutput? ): RenderTester { val expectedWorker = ExpectedWorker(matchesWhen, key, output) if (output != null) { @@ -143,7 +146,7 @@ internal class RealRenderTester( if (expected.output != null) { check(processedAction == null) @Suppress("UNCHECKED_CAST") - processedAction = handler(expected.output.output as ChildOutputT) + processedAction = handler(expected.output.value as ChildOutputT) } @Suppress("UNCHECKED_CAST") @@ -168,7 +171,7 @@ internal class RealRenderTester( if (expected?.output != null) { check(processedAction == null) @Suppress("UNCHECKED_CAST") - processedAction = handler(expected.output.output as T) + processedAction = handler(expected.output.value as T) } } @@ -202,28 +205,26 @@ internal class RealRenderTester( override fun verifyActionState(block: (newState: StateT) -> Unit) = apply { verifyAction { action -> // Don't care about output. - val (newState, _) = action.applyTo(state, mapOutput = {}) + val (newState, _) = action.applyTo(state) block(newState) } } override fun verifyActionOutput(block: (output: OutputT) -> Unit) = apply { verifyAction { action -> - var outputWasSet = false - action.applyTo(state) { output -> - outputWasSet = true - block(output) - } - if (!outputWasSet) { + val (_, output) = action.applyTo(state) + if (output == null) { throw AssertionError("Expected action to set an output") } + block(output.value) } } override fun verifyNoActionOutput() = apply { verifyAction { action -> - action.applyTo(state) { - throw AssertionError("Expected no output, but action set output to: $it") + val (_, output) = action.applyTo(state) + if (output != null) { + throw AssertionError("Expected no output, but action set output to: ${output.value}") } } } diff --git a/workflow-testing/src/main/java/com/squareup/workflow/testing/RenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow/testing/RenderTester.kt index 4980f481eb..a0e1616227 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow/testing/RenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow/testing/RenderTester.kt @@ -19,6 +19,7 @@ import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.Worker import com.squareup.workflow.Workflow import com.squareup.workflow.WorkflowAction +import com.squareup.workflow.WorkflowOutput import kotlin.reflect.KClass /** @@ -91,7 +92,7 @@ fun * .expectWorker( * matchesWhen = { it is SubmitLoginWorker }, * key = "signin", - * output = EmittedOutput(LoginResponse(success = true)) + * output = WorkflowOutput(LoginResponse(success = true)) * ) * .expectWorkflow( * workflowType = ChildWorkflow::class, @@ -176,7 +177,7 @@ fun * .expectWorker( * matchesWhen = { it is SubmitLoginWorker }, * key = "signin", - * output = EmittedOutput(LoginResponse(success = true)) + * output = WorkflowOutput(LoginResponse(success = true)) * ) * .expectWorkflow( * workflowType = ChildWorkflow::class, @@ -204,7 +205,7 @@ interface RenderTester { * when rendering this workflow. * @param assertProps A function that performs assertions on the props passed to * [renderChild][com.squareup.workflow.RenderContext.renderChild]. - * @param output If non-null, [EmittedOutput.output] will be "emitted" when this workflow is + * @param output If non-null, [WorkflowOutput.value] will be "emitted" when this workflow is * rendered. The [WorkflowAction] used to handle this output can be verified using methods on * [RenderTestResult]. */ @@ -213,7 +214,7 @@ interface RenderTester { rendering: ChildRenderingT, key: String = "", assertProps: (props: ChildPropsT) -> Unit = {}, - output: EmittedOutput? = null + output: WorkflowOutput? = null ): RenderTester /** @@ -222,14 +223,14 @@ interface RenderTester { * @param matchesWhen Predicate used to determine if this matches the worker being ran. * @param key The key passed to [runningWorker][com.squareup.workflow.RenderContext.runningWorker] * when rendering this workflow. - * @param output If non-null, [EmittedOutput.output] will be emitted when this worker is ran. + * @param output If non-null, [WorkflowOutput.value] will be emitted when this worker is ran. * The [WorkflowAction] used to handle this output can be verified using methods on * [RenderTestResult]. */ fun expectWorker( matchesWhen: (otherWorker: Worker<*>) -> Boolean, key: String = "", - output: EmittedOutput? = null + output: WorkflowOutput? = null ): RenderTester /** @@ -268,7 +269,7 @@ interface RenderTester { * the overload of this method that takes a `matchesWhen` parameter. * @param key The key passed to [runningWorker][com.squareup.workflow.RenderContext.runningWorker] * when rendering this workflow. - * @param output If non-null, [EmittedOutput.output] will be emitted when this worker is ran. + * @param output If non-null, [WorkflowOutput.value] will be emitted when this worker is ran. * The [WorkflowAction] used to handle this output can be verified using methods on * [RenderTestResult]. */ @@ -277,7 +278,7 @@ fun RenderTester.expectWorker( doesSameWorkAs: Worker<*>, key: String = "", - output: EmittedOutput? = null + output: WorkflowOutput? = null ): RenderTester = expectWorker( /* ktlint-enable parameter-list-wrapping */ matchesWhen = { it.doesSameWorkAs(doesSameWorkAs) }, @@ -288,4 +289,8 @@ fun /** * Wrapper around a potentially-nullable [OutputT] value. */ -data class EmittedOutput(val output: OutputT) +@Deprecated( + "Use WorkflowOutput", + ReplaceWith("WorkflowOutput", "com.squareup.workflow.WorkflowOutput") +) +typealias EmittedOutput = WorkflowOutput diff --git a/workflow-testing/src/test/java/com/squareup/workflow/testing/RealRenderTesterTest.kt b/workflow-testing/src/test/java/com/squareup/workflow/testing/RealRenderTesterTest.kt index 23c0f5adc9..5f3436814f 100644 --- a/workflow-testing/src/test/java/com/squareup/workflow/testing/RealRenderTesterTest.kt +++ b/workflow-testing/src/test/java/com/squareup/workflow/testing/RealRenderTesterTest.kt @@ -23,6 +23,7 @@ import com.squareup.workflow.Workflow import com.squareup.workflow.WorkflowAction import com.squareup.workflow.WorkflowAction.Companion.noAction import com.squareup.workflow.WorkflowAction.Updater +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.contraMap import com.squareup.workflow.renderChild import com.squareup.workflow.runningWorker @@ -49,12 +50,12 @@ class RealRenderTesterTest { val tester = workflow.renderTester(Unit) .expectWorkflow( OutputWhateverChild::class, rendering = Unit, - output = EmittedOutput(Unit) + output = WorkflowOutput(Unit) ) val failure = assertFailsWith { tester.expectWorkflow( - workflow::class, rendering = Unit, output = EmittedOutput(Unit) + workflow::class, rendering = Unit, output = WorkflowOutput(Unit) ) } @@ -71,11 +72,11 @@ class RealRenderTesterTest { // Don't need an implementation, the test should fail before even calling render. val workflow = Workflow.stateless {} val tester = workflow.renderTester(Unit) - .expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + .expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) val failure = assertFailsWith { tester.expectWorkflow( - workflow::class, rendering = Unit, output = EmittedOutput(Unit) + workflow::class, rendering = Unit, output = WorkflowOutput(Unit) ) } @@ -95,7 +96,7 @@ class RealRenderTesterTest { val tester = workflow.renderTester(Unit) .expectWorkflow( OutputWhateverChild::class, rendering = Unit, - output = EmittedOutput(Unit) + output = WorkflowOutput(Unit) ) // Doesn't throw. @@ -106,10 +107,10 @@ class RealRenderTesterTest { // Don't need an implementation, the test should fail before even calling render. val workflow = Workflow.stateless {} val tester = workflow.renderTester(Unit) - .expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + .expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) val failure = assertFailsWith { - tester.expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + tester.expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) } val failureMessage = failure.message!! @@ -127,11 +128,11 @@ class RealRenderTesterTest { val tester = workflow.renderTester(Unit) .expectWorkflow( workflow::class, rendering = Unit, - output = EmittedOutput(Unit) + output = WorkflowOutput(Unit) ) val failure = assertFailsWith { - tester.expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + tester.expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) } val failureMessage = failure.message!! @@ -148,7 +149,7 @@ class RealRenderTesterTest { // Don't need an implementation, the test should fail before even calling render. val workflow = Workflow.stateless {} val tester = workflow.renderTester(Unit) - .expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + .expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) // Doesn't throw. tester.expectWorker(matchesWhen = { true }) @@ -277,7 +278,7 @@ class RealRenderTesterTest { ) workflow.renderTester(Unit) - .expectWorker(matchesWhen = { true }, output = EmittedOutput(Unit)) + .expectWorker(matchesWhen = { true }, output = WorkflowOutput(Unit)) .render { sink -> val error = assertFailsWith { sink.send(TestAction()) @@ -629,7 +630,7 @@ class RealRenderTesterTest { .expectWorkflow( workflowType = child::class, rendering = Unit, - output = EmittedOutput("output") + output = WorkflowOutput("output") ) .render() @@ -647,7 +648,7 @@ class RealRenderTesterTest { val testResult = workflow.renderTester(Unit) .expectWorker( matchesWhen = { true }, - output = EmittedOutput("output") + output = WorkflowOutput("output") ) .render() diff --git a/workflow-tracing/src/main/java/com/squareup/workflow/diagnostic/tracing/TracingWorkflowInterceptor.kt b/workflow-tracing/src/main/java/com/squareup/workflow/diagnostic/tracing/TracingWorkflowInterceptor.kt index 804cfcb71b..66f5368198 100644 --- a/workflow-tracing/src/main/java/com/squareup/workflow/diagnostic/tracing/TracingWorkflowInterceptor.kt +++ b/workflow-tracing/src/main/java/com/squareup/workflow/diagnostic/tracing/TracingWorkflowInterceptor.kt @@ -37,6 +37,7 @@ import com.squareup.workflow.WorkflowAction import com.squareup.workflow.WorkflowAction.Updater import com.squareup.workflow.WorkflowInterceptor import com.squareup.workflow.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow.WorkflowOutput import com.squareup.workflow.applyTo import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -499,7 +500,7 @@ class TracingWorkflowInterceptor internal constructor( action: WorkflowAction<*, *>, oldState: Any?, newState: Any?, - output: Any? + output: WorkflowOutput? ) { val name = workflowNamesById.getValue(workflowId) @@ -513,7 +514,7 @@ class TracingWorkflowInterceptor internal constructor( "action" to action.toString(), "oldState" to oldState.toString(), "newState" to if (oldState == newState) "{no change}" else newState.toString(), - "output" to output.toString() + "output" to (output?.let { it.value.toString() } ?: "{no output}") ) ), ObjectSnapshot( @@ -578,12 +579,9 @@ class TracingWorkflowInterceptor internal constructor( ) : WorkflowAction { override fun Updater.apply() { val oldState = nextState - var output: O? = null - val (newState, _) = delegate.applyTo(nextState) { - output = it - setOutput(it) - } + val (newState, output) = delegate.applyTo(nextState) nextState = newState + output?.let { setOutput(it.value) } onWorkflowAction( workflowId = session.sessionId, action = delegate,