diff --git a/Workflow/Sources/AnyWorkflowConvertible.swift b/Workflow/Sources/AnyWorkflowConvertible.swift index e72a3bc50..d2f34a61f 100644 --- a/Workflow/Sources/AnyWorkflowConvertible.swift +++ b/Workflow/Sources/AnyWorkflowConvertible.swift @@ -104,3 +104,17 @@ extension AnyWorkflowConvertible where Output == Never { return rendered(in: context, key: key) } } + +extension AnyWorkflowConvertible { + /// Process an `Output` + /// + /// - Parameter apply: On `Output`, mutate `State` as necessary and return new `Output` (or `nil`). + public func onOutput(_ apply: @escaping ((inout Parent.State, Output) -> Parent.Output?)) -> AnyWorkflow> { + return asAnyWorkflow() + .mapOutput { output in + AnyWorkflowAction { state -> Parent.Output? in + apply(&state, output) + } + } + } +} diff --git a/Workflow/Tests/AnyWorkflowTests.swift b/Workflow/Tests/AnyWorkflowTests.swift index 0f404ae7e..7b811e1b9 100644 --- a/Workflow/Tests/AnyWorkflowTests.swift +++ b/Workflow/Tests/AnyWorkflowTests.swift @@ -35,6 +35,25 @@ public class AnyWorkflowTests: XCTestCase { XCTAssertEqual(node.render(), "fdsadsa") } + + func testOnOutput() { + let host = WorkflowHost(workflow: OnOutputWorkflow()) + + let renderingExpectation = expectation(description: "Waiting for rendering") + host.rendering.producer.startWithValues { rendering in + if rendering { + renderingExpectation.fulfill() + } + } + + let outputExpectation = expectation(description: "Waiting for output") + host.output.observeValues { output in + if output { + outputExpectation.fulfill() + } + } + wait(for: [renderingExpectation, outputExpectation], timeout: 1) + } } /// Has no state or output, simply renders a reversed string @@ -70,3 +89,38 @@ extension SimpleWorkflow { return String(string.reversed()) } } + +private struct OnOutputWorkflow: Workflow { + typealias State = Bool + typealias Rendering = Bool + typealias Output = Bool + + func makeInitialState() -> Bool { + false + } + + func workflowDidChange(from previousWorkflow: OnOutputWorkflow, state: inout Bool) {} + + func render(state: State, context: RenderContext) -> Bool { + OnOutputChildWorkflow() + .onOutput { state, output in + state = output + return output + } + .running(in: context) + return state + } +} + +private struct OnOutputChildWorkflow: Workflow { + typealias State = Void + typealias Output = Bool + typealias Rendering = Void + + func render(state: Void, context: RenderContext) { + let sink = context.makeOutputSink() + DispatchQueue.main.async { + sink.send(true) + } + } +}