Skip to content
Draft
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
26 changes: 24 additions & 2 deletions Workflow/Sources/WorkflowHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,27 @@ public final class WorkflowHost<WorkflowType: Workflow> {
}
}

/// When `true`, the host will not render its `rootNode` unless using the
/// `managedUpdate(workflow:)` function.
///
/// This property can be used when fine-grained control over the rendering
/// of a `WorkflowHost` is needed, like cases where a container maintains
/// hosts and propagates their output & renderings to the main Workflow
/// tree. Without this mechanism, detached hosts may render extra times:
/// once for the applied action and again when the main tree renders.
@_spi(WorkflowHostManagement)
public var managedRenderings: Bool = false

/// Executes `update(workflow:)`, temporarily allowing the `rootNode` to be
/// rendered when `managedRenderings` is `true`.
@_spi(WorkflowHostManagement)
public func managedUpdate(workflow: WorkflowType) {
let previousValue = managedRenderings
managedRenderings = false
update(workflow: workflow)
managedRenderings = previousValue
}

/// Update the input for the workflow. Will cause a render pass.
public func update(workflow: WorkflowType) {
rootNode.update(workflow: workflow)
Expand Down Expand Up @@ -144,8 +165,9 @@ extension WorkflowHost {
// We can skip the render pass if:
// 1. The runtime config supports this behavior.
// 2. No subtree invalidation occurred during action processing.
context.runtimeConfig.renderOnlyIfStateChanged
&& !output.subtreeInvalidated
// 3. Alternatively, if the host's rendering is managed externally.
(context.runtimeConfig.renderOnlyIfStateChanged
&& !output.subtreeInvalidated) || managedRenderings
}
}

Expand Down
40 changes: 40 additions & 0 deletions Workflow/Tests/WorkflowHostTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import XCTest
@_spi(WorkflowRuntimeConfig) @testable import Workflow
@_spi(WorkflowHostManagement) import Workflow

final class WorkflowHostTests: XCTestCase {
func test_updatedInputCausesRenderPass() {
Expand Down Expand Up @@ -87,6 +88,45 @@ final class WorkflowHost_EventEmissionTests: XCTestCase {
}
}

// MARK: Host Management

extension WorkflowHostTests {
func test_managed_renderings() {
let host = WorkflowHost(
workflow: TestWorkflow(step: .first)
)
host.managedRenderings = true
XCTAssertEqual(host.rendering.value, 1)

// Example of a standard render pass. This will not render the hosted
// workflow when renderings are managed. This render pass could also
// come from an action applied to the workflow.
host.update(workflow: TestWorkflow(step: .second))
XCTAssertEqual(host.rendering.value, 1)

// A managed update will always render the workflow.
host.managedUpdate(workflow: TestWorkflow(step: .second))
XCTAssertEqual(host.rendering.value, 2)

// Ensure that the flag is still enabled.
XCTAssertTrue(host.managedRenderings)
}

func test_managed_renderings_when_not_set() {
let host = WorkflowHost(
workflow: TestWorkflow(step: .first)
)
XCTAssertEqual(host.rendering.value, 1)

// A managed update will always render the workflow.
host.managedUpdate(workflow: TestWorkflow(step: .second))
XCTAssertEqual(host.rendering.value, 2)

// Ensure that the flag wasn't inadvertently enabled.
XCTAssertFalse(host.managedRenderings)
}
}

// MARK: Runtime Configuration

extension WorkflowHostTests {
Expand Down
Loading