Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
.macOS(.v12),
.watchOS(.v8),
.macCatalyst(.v16),
.tvOS(.v12),
.tvOS(.v13),
],
products: [
// MARK: Workflow
Expand Down Expand Up @@ -71,7 +71,6 @@ let package = Package(
name: "Workflow",
dependencies: [
.product(name: "IssueReporting", package: "xctest-dynamic-overlay"),
.product(name: "ReactiveSwift", package: "ReactiveSwift"),
],
path: "Workflow/Sources"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class RootWorkflowTests: XCTestCase {

// First rendering is just the welcome screen. Update the name.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -100,7 +100,7 @@ class RootWorkflowTests: XCTestCase {

// Log in and go to the todo list.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -113,7 +113,7 @@ class RootWorkflowTests: XCTestCase {

// Expect the todo list to be rendered. Edit the first todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -134,7 +134,7 @@ class RootWorkflowTests: XCTestCase {

// Selected a todo to edit. Expect the todo edit screen.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -158,7 +158,7 @@ class RootWorkflowTests: XCTestCase {

// Save the selected todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down Expand Up @@ -204,7 +204,7 @@ class RootWorkflowTests: XCTestCase {

// Expect the todo list. Validate the title was updated.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down
12 changes: 6 additions & 6 deletions Samples/Tutorial/Tutorial5.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// First rendering is just the welcome screen. Update the name.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -672,7 +672,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// Log in and go to the todo list.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(1, backStack.items.count)

guard let welcomeScreen = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -685,7 +685,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// Expect the todo list to be rendered. Edit the first todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -706,7 +706,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// Selected a todo to edit. Expect the todo edit screen.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand All @@ -730,7 +730,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// Save the selected todo.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(3, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down Expand Up @@ -779,7 +779,7 @@ Add another test to `RootWorkflowTests`. We will run the tree of workflows in a

// Expect the todo list. Validate the title was updated.
do {
let backStack = workflowHost.rendering.value
let backStack = workflowHost.rendering
XCTAssertEqual(2, backStack.items.count)

guard let _ = backStack.items[0].screen.wrappedScreen as? WelcomeScreen else {
Expand Down
37 changes: 25 additions & 12 deletions Workflow/Sources/WorkflowHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

import Combine
import Dispatch
import ReactiveSwift

/// Defines a type that receives debug information about a running workflow hierarchy.
public protocol WorkflowDebugger {
Expand All @@ -33,16 +33,25 @@ public protocol WorkflowDebugger {

/// Manages an active workflow hierarchy.
public final class WorkflowHost<WorkflowType: Workflow> {
private let (outputEvent, outputEventObserver) = Signal<WorkflowType.Output, Never>.pipe()
private let outputSubject = PassthroughSubject<WorkflowType.Output, Never>()
Comment thread
amorde marked this conversation as resolved.

// @testable
let rootNode: WorkflowNode<WorkflowType>

private let mutableRendering: MutableProperty<WorkflowType.Rendering>
private let renderingSubject: CurrentValueSubject<WorkflowType.Rendering, Never>

/// Represents the `Rendering` produced by the root workflow in the hierarchy. New `Rendering` values are produced
/// The current `Rendering` produced by the root workflow in the hierarchy. A new `Rendering` value is produced
/// as state transitions occur within the hierarchy.
public let rendering: Property<WorkflowType.Rendering>
public var rendering: WorkflowType.Rendering {
renderingSubject.value
}

/// A publisher of the `Rendering` values produced by the root workflow in the hierarchy. Emits the current
/// `Rendering` when subscribed to, followed by a new value after each subsequent render pass. Use
/// `dropFirst()` to observe only changes.
public var renderingPublisher: AnyPublisher<WorkflowType.Rendering, Never> {
renderingSubject.eraseToAnyPublisher()
}

/// Context object to pass down to descendant nodes in the tree.
let context: HostContext
Expand Down Expand Up @@ -88,8 +97,7 @@ public final class WorkflowHost<WorkflowType: Workflow> {
parentSession: nil
)

self.mutableRendering = MutableProperty(rootNode.render())
self.rendering = Property(mutableRendering)
self.renderingSubject = CurrentValueSubject(rootNode.render())
rootNode.enableEvents()

debugger?.didEnterInitialState(snapshot: rootNode.makeDebugSnapshot())
Expand All @@ -99,6 +107,11 @@ public final class WorkflowHost<WorkflowType: Workflow> {
}
}

deinit {
renderingSubject.send(completion: .finished)
outputSubject.send(completion: .finished)
}

/// Update the input for the workflow. Will cause a render pass.
public func update(workflow: WorkflowType) {
if context.runtimeConfig.useSinkEventHandler {
Expand Down Expand Up @@ -130,12 +143,12 @@ public final class WorkflowHost<WorkflowType: Workflow> {
private func handle(output: WorkflowNode<WorkflowType>.Output) {
let shouldRender = !shouldSkipRenderForOutput(output)
if shouldRender {
mutableRendering.value = rootNode.render()
renderingSubject.send(rootNode.render())
}

// Always emit an output, regardless of whether a render occurs
if let outputEvent = output.outputEvent {
outputEventObserver.send(value: outputEvent)
outputSubject.send(outputEvent)
}

debugger?.didUpdate(
Expand All @@ -149,9 +162,9 @@ public final class WorkflowHost<WorkflowType: Workflow> {
}
}

/// A signal containing output events emitted by the root workflow in the hierarchy.
public var output: Signal<WorkflowType.Output, Never> {
outputEvent
/// A publisher of the output events emitted by the root workflow in the hierarchy.
public var outputPublisher: AnyPublisher<WorkflowType.Output, Never> {
outputSubject.eraseToAnyPublisher()
}
}

Expand Down
9 changes: 6 additions & 3 deletions Workflow/Tests/AnyWorkflowTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import ReactiveSwift
import Combine
import XCTest
@testable import Workflow

Expand All @@ -40,18 +40,21 @@ public class AnyWorkflowTests: XCTestCase {
let host = WorkflowHost(workflow: OnOutputWorkflow())

let renderingExpectation = expectation(description: "Waiting for rendering")
host.rendering.producer.startWithValues { rendering in
let renderingCancellable = host.renderingPublisher.dropFirst().sink { rendering in
if rendering {
renderingExpectation.fulfill()
}
}
defer { renderingCancellable.cancel() }

let outputExpectation = expectation(description: "Waiting for output")
host.output.observeValues { output in
let outputCancellable = host.outputPublisher.sink { output in
if output {
outputExpectation.fulfill()
}
}
defer { outputCancellable.cancel() }

wait(for: [renderingExpectation, outputExpectation], timeout: 1)
}

Expand Down
Loading
Loading