From ebc902fb6d4918f70e85f7a0ae8226c4f40b26d9 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Tue, 2 Nov 2021 11:09:43 -0500 Subject: [PATCH 01/18] [launch-with-workflow] - Added FlowRepresentableMetadataTests, made FlowRepresentableMetadata open - RAG MZ Co-authored-by: Richard Gist --- .../Models/FlowRepresentableMetadata.swift | 2 +- ...owRepresentableMetadataConsumerTests.swift | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift diff --git a/Sources/SwiftCurrent/Models/FlowRepresentableMetadata.swift b/Sources/SwiftCurrent/Models/FlowRepresentableMetadata.swift index 01bcca4f6..b86e50622 100644 --- a/Sources/SwiftCurrent/Models/FlowRepresentableMetadata.swift +++ b/Sources/SwiftCurrent/Models/FlowRepresentableMetadata.swift @@ -14,7 +14,7 @@ import Foundation ### Discussion Every time a `Workflow` is created, the defining characteristics about a `FlowRepresentable` are stored in the `FlowRepresentableMetadata` to be used later. */ -public class FlowRepresentableMetadata { +open class FlowRepresentableMetadata { /// Preferred `LaunchStyle` of the associated `FlowRepresentable`. public private(set) var launchStyle: LaunchStyle /// Preferred `FlowPersistence` of the associated `FlowRepresentable`; set when `FlowRepresentableMetadata` instantiates an instance. diff --git a/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift new file mode 100644 index 000000000..3496924d2 --- /dev/null +++ b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift @@ -0,0 +1,44 @@ +// +// FlowRepresentableMetadataConsumerTests.swift +// SwiftCurrent +// +// Created by Morgan Zellers on 11/2/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import XCTest +import SwiftCurrent + +class FlowRepresentableMetadataConsumerTests: XCTestCase { + + + func testOverridingFlowRepresentbleMetadata() { + class SpecialConformanceClass { } + + class NewMetadata: FlowRepresentableMetadata { + var wf: String? // AnyWFItem + + private override init(_ flowRepresentableType: FR.Type, launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) where FR : FlowRepresentable { + + + super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory) + } + + convenience init(flowRepresentableType: FR.Type, launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) { + + self.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory) + + wf = String(describing: flowRepresentableType) + } + } + + final class FR1: SpecialConformanceClass, FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + + let foo = NewMetadata(flowRepresentableType: FR1.self, + flowPersistence: { _ in .default }) { _ in + AnyFlowRepresentable(FR1.self, args: .none) + } + } +} From 21a62c8f920806c9372912f78a74958e60328496 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Tue, 2 Nov 2021 14:57:49 -0500 Subject: [PATCH 02/18] [launch-with-workflow] - Added tests for new launcher initializer using AnyWorkflow - RAG MZ Co-authored-by: Richard Gist --- .../Extensions/WorkflowExtensions.swift | 13 +++++++ .../ExtendedFlowRepresentableMetadata.swift | 38 +++++++++++++++++++ .../TypeErased/AnyWorkflowItem.swift | 23 +++++++++++ .../Views/WorkflowLauncher.swift | 32 ++++++++++++++++ ...owRepresentableMetadataConsumerTests.swift | 2 +- .../SwiftCurrent_SwiftUITests.swift | 19 ++++++++++ .../ViewInspector/InspectableExtensions.swift | 2 + .../WorkflowLauncherTests.swift | 15 ++++++++ 8 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift create mode 100644 Sources/SwiftCurrent_SwiftUI/TypeErased/AnyWorkflowItem.swift create mode 100644 Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 347fb4506..7b9123e1a 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -11,6 +11,19 @@ import SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension Workflow where F: FlowRepresentable & View { + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + */ + public convenience init(_ type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) { + self.init(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + } + /// Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. public func abandon() { AnyWorkflow(self).abandon() diff --git a/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift new file mode 100644 index 000000000..a60efcf77 --- /dev/null +++ b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift @@ -0,0 +1,38 @@ +// +// ExtendedFlowRepresentableMetadata.swift +// SwiftCurrent +// +// Created by Morgan Zellers on 11/2/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import SwiftUI +import SwiftCurrent + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata { + private(set) var workflowItemFactory: (AnyWorkflowItem?) -> AnyWorkflowItem + + init(flowRepresentableType: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, + flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) { + workflowItemFactory = { _ in + AnyWorkflowItem(view: WorkflowItem(flowRepresentableType)) + } + + super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory) + } + + init(flowRepresentableType: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) { + workflowItemFactory = { _ in + AnyWorkflowItem(view: WorkflowItem(flowRepresentableType)) + } + + super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence) { args in + AnyFlowRepresentableView(type: FR.self, args: args) + } + } +} diff --git a/Sources/SwiftCurrent_SwiftUI/TypeErased/AnyWorkflowItem.swift b/Sources/SwiftCurrent_SwiftUI/TypeErased/AnyWorkflowItem.swift new file mode 100644 index 000000000..3211c9bc4 --- /dev/null +++ b/Sources/SwiftCurrent_SwiftUI/TypeErased/AnyWorkflowItem.swift @@ -0,0 +1,23 @@ +// +// AnyWorkflowItem.swift +// SwiftCurrent +// +// Created by Morgan Zellers on 11/2/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import SwiftUI + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +public struct AnyWorkflowItem: View { + let inspection = Inspection() + private let _body: AnyView + + public var body: some View { + _body.onReceive(inspection.notice) { inspection.visit(self, $0) } + } + + init(view: WorkflowItem) { + _body = AnyView(view) + } +} diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index c7f940293..62915625f 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -77,6 +77,38 @@ public struct WorkflowLauncher: View { .onReceive(inspection.notice) { inspection.visit(self, $0) } } + /** + Creates a base for proceeding with a `WorkflowItem`. + - Parameter isLaunched: binding that controls launching the underlying `Workflow`. + - Parameter workflow: workflow that holds the `WorkflowItem` + */ + public init(isLaunched: Binding, workflow: Workflow) where Content == AnyWorkflowItem { + self.init(isLaunched: isLaunched, workflow: AnyWorkflow(workflow)) + } + + /** + Creates a base for proceeding with a `WorkflowItem`. + - Parameter isLaunched: binding that controls launching the underlying `Workflow`. + - Parameter workflow: workflow that holds the `WorkflowItem` + */ + private init(isLaunched: Binding, workflow: AnyWorkflow) where Content == AnyWorkflowItem { + workflow.forEach { + assert($0.value.metadata is ExtendedFlowRepresentableMetadata) + } + _isLaunched = isLaunched + let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: .none) + _model = StateObject(wrappedValue: model) + _launcher = StateObject(wrappedValue: Launcher(workflow: workflow, + responder: model, + launchArgs: .none)) + + guard let workflowItem = (workflow.first?.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(nil) else { + fatalError("WorkflowItem was nil") + } + + _content = State(wrappedValue: workflowItem) + } + /** Creates a base for proceeding with a `WorkflowItem`. - Parameter isLaunched: binding that controls launching the underlying `Workflow`. diff --git a/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift index 3496924d2..d6b6dd928 100644 --- a/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift +++ b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift @@ -36,7 +36,7 @@ class FlowRepresentableMetadataConsumerTests: XCTestCase { var _workflowPointer: AnyFlowRepresentable? } - let foo = NewMetadata(flowRepresentableType: FR1.self, + let _ = NewMetadata(flowRepresentableType: FR1.self, flowPersistence: { _ in .default }) { _ in AnyFlowRepresentable(FR1.self, args: .none) } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index e8f8d57e3..c0f33b060 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -731,6 +731,25 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [exp], timeout: TestConstant.timeout) } + + func testLaunchingAWorkflowFromAnAnyWorkflow() { + struct FR1: View, FlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + + var body: some View { + Button("Proceed") { proceedInWorkflow() } + } + } + + let wf = Workflow(FR1.self) + let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + + let exp = ViewHosting.loadView(launcher).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self)) + } + + wait(for: [exp], timeout: TestConstant.timeout) + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) diff --git a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift index e14f62d80..ad16caa78 100644 --- a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift +++ b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift @@ -15,6 +15,8 @@ import SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension WorkflowItem: Inspectable { } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension AnyWorkflowItem: Inspectable { } +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension WorkflowLauncher: Inspectable { } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension ViewControllerWrapper: Inspectable { } diff --git a/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift b/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift new file mode 100644 index 000000000..d348391d1 --- /dev/null +++ b/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift @@ -0,0 +1,15 @@ +// +// WorkflowLauncherTests.swift +// SwiftCurrent +// +// Created by Morgan Zellers on 11/2/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import SwiftCurrent +import XCTest + + +final class WorkflowLauncherTests: XCTestCase { + +} From 41474c3bc0202459e90c111300f3236867385313 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Wed, 3 Nov 2021 10:00:52 -0500 Subject: [PATCH 03/18] Adding support for .last computed property to AnyWorkflow --- Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift b/Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift index eede95ba4..ea6f49eae 100644 --- a/Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift +++ b/Sources/SwiftCurrent/TypeErased/AnyWorkflow.swift @@ -34,6 +34,9 @@ public class AnyWorkflow { /// The first `LinkedList.Node` of the wrapped `Workflow`. public var first: Element? { storageBase.first } + /// The last `LinkedList.Node` of the wrapped `Workflow`. + public var last: Element? { storageBase.last } + fileprivate var storageBase: AnyWorkflowStorageBase /// Creates a type erased `Workflow`. @@ -111,6 +114,7 @@ fileprivate class AnyWorkflowStorageBase { var orchestrationResponder: OrchestrationResponder? var count: Int { fatalError("count not overridden by AnyWorkflowStorage") } var first: LinkedList<_WorkflowItem>.Element? { fatalError("first not overridden by AnyWorkflowStorage") } + var last: LinkedList<_WorkflowItem>.Element? { fatalError("last not overridden by AnyWorkflowStorage") } // https://github.com/wwt/SwiftCurrent/blob/main/.github/STYLEGUIDE.md#type-erasure // swiftlint:disable:next unavailable_function @@ -157,6 +161,7 @@ fileprivate final class AnyWorkflowStorage: AnyWorkflowSto override var count: Int { workflow.count } override var first: LinkedList<_WorkflowItem>.Element? { workflow.first } + override var last: LinkedList<_WorkflowItem>.Element? { workflow.last } init(_ workflow: Workflow) { self.workflow = workflow From e5b0e64f96498c520ce239fee22569c5a8b2c156 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Wed, 3 Nov 2021 12:30:07 -0500 Subject: [PATCH 04/18] Adds support for workflows with more than one item. --- .../Extensions/WorkflowExtensions.swift | 32 +++++++++++++++++++ .../ExtendedFlowRepresentableMetadata.swift | 16 +++++++--- .../Views/WorkflowItem.swift | 24 +++++++------- .../Views/WorkflowLauncher.swift | 19 +++++++++-- .../SwiftCurrent_SwiftUITests.swift | 24 ++++++++++++++ .../WorkflowLauncherTests.swift | 15 --------- 6 files changed, 96 insertions(+), 34 deletions(-) delete mode 100644 Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 7b9123e1a..7ab668030 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -28,4 +28,36 @@ extension Workflow where F: FlowRepresentable & View { public func abandon() { AnyWorkflow(self).abandon() } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { flowPersistence($0) }) + return workflow + } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return workflow + } } diff --git a/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift index a60efcf77..46526ff82 100644 --- a/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift +++ b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift @@ -17,8 +17,12 @@ class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata { launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) { - workflowItemFactory = { _ in - AnyWorkflowItem(view: WorkflowItem(flowRepresentableType)) + workflowItemFactory = { + if let wrappedWorkflowItem = $0 { + return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) + } else { + return AnyWorkflowItem(view: WorkflowItem(FR.self)) + } } super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory) @@ -27,8 +31,12 @@ class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata { init(flowRepresentableType: FR.Type, launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) { - workflowItemFactory = { _ in - AnyWorkflowItem(view: WorkflowItem(flowRepresentableType)) + workflowItemFactory = { + if let wrappedWorkflowItem = $0 { + return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) + } else { + return AnyWorkflowItem(view: WorkflowItem(FR.self)) + } } super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence) { args in diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift index e18179899..3a04f63bf 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowItem.swift @@ -89,26 +89,26 @@ public struct WorkflowItem Wrapped) where Content == F { - let metadata = FlowRepresentableMetadata(Content.self, - launchStyle: .new, - flowPersistence: flowPersistenceClosure, - flowRepresentableFactory: factory) + let metadata = ExtendedFlowRepresentableMetadata(flowRepresentableType: Content.self, + launchStyle: .new, + flowPersistence: flowPersistenceClosure, + flowRepresentableFactory: factory) _metadata = State(initialValue: metadata) _wrapped = State(initialValue: wrapped()) } diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index 62915625f..c2c8c1956 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -102,11 +102,24 @@ public struct WorkflowLauncher: View { responder: model, launchArgs: .none)) - guard let workflowItem = (workflow.first?.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(nil) else { - fatalError("WorkflowItem was nil") + /* Maybe pull the below into its own function? */ + let lastMetadata = workflow.last?.value.metadata as? ExtendedFlowRepresentableMetadata + let lastItem = lastMetadata?.workflowItemFactory(nil) + + if let headItem = WorkflowLauncher.findHeadItem(element: workflow.last, item: lastItem) { + _content = State(wrappedValue: headItem) + } else if let lastItem = lastItem { + _content = State(wrappedValue: lastItem) + } else { + fatalError("Workflow has no items to launch") } + } + + static func findHeadItem(element: AnyWorkflow.Element?, item: AnyWorkflowItem?) -> AnyWorkflowItem? { + guard let previous = element?.previous, + let previousItem = (previous.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(item) else { return item } - _content = State(wrappedValue: workflowItem) + return findHeadItem(element: previous, item: previousItem) } /** diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index c0f33b060..09cdae257 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -750,6 +750,30 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [exp], timeout: TestConstant.timeout) } + + func testLaunchingLongerWorkflowFromAnyWorkflow() { + struct FR1: View, FlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + + var body: some View { + Button("Proceed") { proceedInWorkflow() } + } + } + + let wf = Workflow(FR1.self) + .thenProceed(with: FR1.self) { .default } + .thenProceed(with: FR1.self) { .default } + .thenProceed(with: FR1.self) { .default } + .thenProceed(with: FR1.self) { .default } + + let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + + let exp = ViewHosting.loadView(launcher).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self)) + } + + wait(for: [exp], timeout: TestConstant.timeout) + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) diff --git a/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift b/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift deleted file mode 100644 index d348391d1..000000000 --- a/Tests/SwiftCurrent_SwiftUITests/WorkflowLauncherTests.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// WorkflowLauncherTests.swift -// SwiftCurrent -// -// Created by Morgan Zellers on 11/2/21. -// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. -// - -import SwiftCurrent -import XCTest - - -final class WorkflowLauncherTests: XCTestCase { - -} From b3c2c6b9c4daeb4bc7f32d230237771e813b1429 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Wed, 3 Nov 2021 13:24:16 -0500 Subject: [PATCH 05/18] Refactor of ExtendedFlowRepresentableMetadata inits and function to find starting content item in WorkflowLauncher. --- .../ExtendedFlowRepresentableMetadata.swift | 14 ++----- .../Views/WorkflowLauncher.swift | 39 ++++++++++--------- .../SwiftCurrent_SwiftUITests.swift | 21 +--------- 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift index 46526ff82..760ba21b0 100644 --- a/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift +++ b/Sources/SwiftCurrent_SwiftUI/Models/ExtendedFlowRepresentableMetadata.swift @@ -18,11 +18,8 @@ class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata { flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence, flowRepresentableFactory: @escaping (AnyWorkflow.PassedArgs) -> AnyFlowRepresentable) { workflowItemFactory = { - if let wrappedWorkflowItem = $0 { - return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) - } else { - return AnyWorkflowItem(view: WorkflowItem(FR.self)) - } + guard let wrappedWorkflowItem = $0 else { return AnyWorkflowItem(view: WorkflowItem(FR.self)) } + return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) } super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence, flowRepresentableFactory: flowRepresentableFactory) @@ -32,11 +29,8 @@ class ExtendedFlowRepresentableMetadata: FlowRepresentableMetadata { launchStyle: LaunchStyle = .default, flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) { workflowItemFactory = { - if let wrappedWorkflowItem = $0 { - return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) - } else { - return AnyWorkflowItem(view: WorkflowItem(FR.self)) - } + guard let wrappedWorkflowItem = $0 else { return AnyWorkflowItem(view: WorkflowItem(FR.self)) } + return AnyWorkflowItem(view: WorkflowItem(FR.self) { wrappedWorkflowItem }) } super.init(flowRepresentableType, launchStyle: launchStyle, flowPersistence: flowPersistence) { args in diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index c2c8c1956..682902d2c 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -102,24 +102,7 @@ public struct WorkflowLauncher: View { responder: model, launchArgs: .none)) - /* Maybe pull the below into its own function? */ - let lastMetadata = workflow.last?.value.metadata as? ExtendedFlowRepresentableMetadata - let lastItem = lastMetadata?.workflowItemFactory(nil) - - if let headItem = WorkflowLauncher.findHeadItem(element: workflow.last, item: lastItem) { - _content = State(wrappedValue: headItem) - } else if let lastItem = lastItem { - _content = State(wrappedValue: lastItem) - } else { - fatalError("Workflow has no items to launch") - } - } - - static func findHeadItem(element: AnyWorkflow.Element?, item: AnyWorkflowItem?) -> AnyWorkflowItem? { - guard let previous = element?.previous, - let previousItem = (previous.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(item) else { return item } - - return findHeadItem(element: previous, item: previousItem) + _content = State(wrappedValue: WorkflowLauncher.itemToLaunch(from: workflow)) } /** @@ -202,6 +185,26 @@ public struct WorkflowLauncher: View { onFinish.forEach { $0(args) } } + private static func itemToLaunch(from workflow: AnyWorkflow) -> AnyWorkflowItem { + let lastMetadata = workflow.last?.value.metadata as? ExtendedFlowRepresentableMetadata + let lastItem = lastMetadata?.workflowItemFactory(nil) + + if let headItem = WorkflowLauncher.findHeadItem(element: workflow.last, item: lastItem) { + return headItem + } else if let lastItem = lastItem { + return lastItem + } + + fatalError("Workflow has no items to launch") + } + + private static func findHeadItem(element: AnyWorkflow.Element?, item: AnyWorkflowItem?) -> AnyWorkflowItem? { + guard let previous = element?.previous, + let previousItem = (previous.value.metadata as? ExtendedFlowRepresentableMetadata)?.workflowItemFactory(item) else { return item } + + return findHeadItem(element: previous, item: previousItem) + } + /// Adds an action to perform when this `Workflow` has finished. public func onFinish(closure: @escaping (AnyWorkflow.PassedArgs) -> Void) -> Self { var onFinish = self.onFinish diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index 09cdae257..f5feb8f07 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -741,31 +741,12 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } - let wf = Workflow(FR1.self) - let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) - - let exp = ViewHosting.loadView(launcher).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self)) - } - - wait(for: [exp], timeout: TestConstant.timeout) - } - - func testLaunchingLongerWorkflowFromAnyWorkflow() { - struct FR1: View, FlowRepresentable, Inspectable { - weak var _workflowPointer: AnyFlowRepresentable? - - var body: some View { - Button("Proceed") { proceedInWorkflow() } - } - } - let wf = Workflow(FR1.self) .thenProceed(with: FR1.self) { .default } .thenProceed(with: FR1.self) { .default } .thenProceed(with: FR1.self) { .default } .thenProceed(with: FR1.self) { .default } - + let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) let exp = ViewHosting.loadView(launcher).inspection.inspect { view in From 402a9fe1d1a74cca05aa5b8720c4c555c41aefc8 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 4 Nov 2021 09:58:17 -0500 Subject: [PATCH 06/18] Added test for fatalError scenario in which no items in a workflow --- .../SwiftCurrent_SwiftUITests.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index f5feb8f07..fa7d49054 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -755,6 +755,23 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [exp], timeout: TestConstant.timeout) } + + func testIfNoWorkflowItemsThenFatalError() throws { + struct FR1: View, FlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + + var body: some View { + Button("Proceed") { proceedInWorkflow() } + } + } + + let wf = Workflow(FR1.self) + wf.removeLast() + + try XCTAssertThrowsFatalError { + _ = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + } + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) From e67d734877e332225e117075deab5fb6b3bb169b Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 4 Nov 2021 15:14:35 -0500 Subject: [PATCH 07/18] WIP - Working through the tests and adding thenProceeds as I figure it out --- .../Extensions/WorkflowExtensions.swift | 28 +++++- .../SwiftCurrent_SwiftUITests.swift | 97 ++++++++++++++++++- 2 files changed, 117 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 7ab668030..226fa150c 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -38,13 +38,16 @@ extension Workflow where F: FlowRepresentable & View { */ public func thenProceed(with type: F.Type, launchStyle: LaunchStyle = .default, - flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { let workflow = Workflow(first) workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, - launchStyle: launchStyle) { flowPersistence($0) }) + launchStyle: launchStyle) { _ in flowPersistence() }) return workflow } +} +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == Never { /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. @@ -54,7 +57,26 @@ extension Workflow where F: FlowRepresentable & View { */ public func thenProceed(with type: F.Type, launchStyle: LaunchStyle = .default, - flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return workflow + } +} + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWorkflow.PassedArgs { + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { let workflow = Workflow(first) workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, launchStyle: launchStyle) { _ in flowPersistence() }) diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index fa7d49054..9642fdebf 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -732,7 +732,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [exp], timeout: TestConstant.timeout) } - func testLaunchingAWorkflowFromAnAnyWorkflow() { + func testLaunchingAWorkflowWithOneItemFromAnAnyWorkflow() { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? @@ -742,10 +742,6 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let wf = Workflow(FR1.self) - .thenProceed(with: FR1.self) { .default } - .thenProceed(with: FR1.self) { .default } - .thenProceed(with: FR1.self) { .default } - .thenProceed(with: FR1.self) { .default } let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) @@ -756,6 +752,97 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [exp], timeout: TestConstant.timeout) } + func testLaunchingAMultiTypeLongWorkflowFromAnAnyWorkflow() { + struct FR1: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR1 type") } + } + struct FR2: View, FlowRepresentable, Inspectable { // Passthrough + typealias WorkflowOutput = AnyWorkflow.PassedArgs + var _workflowPointer: AnyFlowRepresentable? + private let data: AnyWorkflow.PassedArgs + var body: some View { Text("FR2 type") } + + init(with data: AnyWorkflow.PassedArgs) { + self.data = data + } + } +// struct FR3: View, FlowRepresentable, Inspectable { +// let str: String +// init(with str: String) { +// self.str = str +// } +// var _workflowPointer: AnyFlowRepresentable? +// var body: some View { Text("FR3 type, \(str)") } +// } +// struct FR4: View, FlowRepresentable, Inspectable { +// var _workflowPointer: AnyFlowRepresentable? +// var body: some View { Text("FR4 type") } +// } + + let expectOnFinish = expectation(description: "OnFinish called") + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self) + .thenProceed(with: FR2.self) { .default } + + let expectViewLoaded = ViewHosting.loadView( + WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + .onFinish { _ in + expectOnFinish.fulfill() + }).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") + XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow(.args(expectedArgs))) + } + } + + wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + } + + func testLaunchingAWorkflowFromAnAnyWorkflow() { + struct FR1: View, FlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR1 type") } + } + struct FR2: View, PassthroughFlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR2 type") } + } + struct FR3: View, FlowRepresentable, Inspectable { + weak var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR3 type") } + } + + let wf = Workflow(FR1.self) + .thenProceed(with: FR2.self, flowPersistence: { .default }) + .thenProceed(with: FR3.self, flowPersistence: { .default }) + + let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + let expectOnFinish = expectation(description: "OnFinish called") + + let expectViewLoaded = ViewHosting.loadView( + launcher + .onFinish { _ in + expectOnFinish.fulfill() + }).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") + XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR3.self).text().string(), "FR3 type") + XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) + } + } + } + + wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + } + func testIfNoWorkflowItemsThenFatalError() throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? From 3e70c5362f9ce427f882f372be35523494579fe6 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Fri, 5 Nov 2021 14:20:54 -0500 Subject: [PATCH 08/18] WIP still - added more thenProceeds but still have a lot to drive out. --- .../Extensions/WorkflowExtensions.swift | 112 +++++++++++++++++- .../SwiftCurrent_SwiftUITests.swift | 64 +++++++++- 2 files changed, 169 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 226fa150c..54aff64c2 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -36,14 +36,40 @@ extension Workflow where F: FlowRepresentable & View { - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ - public func thenProceed(with type: F.Type, - launchStyle: LaunchStyle = .default, - flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { - let workflow = Workflow(first) + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowOutput == FR.WorkflowInput { + let workflow = Workflow(first) workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, - launchStyle: launchStyle) { _ in flowPersistence() }) + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? FR.WorkflowInput else { return .default } + + return flowPersistence(cast) + }) return workflow } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ +// public func thenProceed(with type: F.Type, +// launchStyle: LaunchStyle = .default, +// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { +// let workflow = Workflow(first) +// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, +// launchStyle: launchStyle) { data in +// guard case.args(let extracted) = data, +// let cast = extracted as? F.WorkflowInput else { return .default } +// +// return flowPersistence(cast) +// }) +// return workflow +// } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) @@ -63,10 +89,63 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == Never launchStyle: launchStyle) { _ in flowPersistence() }) return workflow } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return workflow + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWorkflow.PassedArgs { + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ +// public func thenProceed(with type: F.Type, +// launchStyle: LaunchStyle = .default, +// flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { +// let workflow = Workflow(first) +// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, +// launchStyle: launchStyle) { _ in flowPersistence() }) +// return workflow +// } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ +// public func thenProceed(with type: F.Type, +// launchStyle: LaunchStyle = .default, +// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { +// let workflow = Workflow(first) +// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, +// launchStyle: launchStyle) { data in +// guard case.args(let extracted) = data, +// let cast = extracted as? F.WorkflowInput else { return .default } +// +// return flowPersistence(cast) +// }) +// return workflow +// } + /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. @@ -76,10 +155,31 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWor */ public func thenProceed(with type: F.Type, launchStyle: LaunchStyle = .default, - flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == Never { let workflow = Workflow(first) workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, launchStyle: launchStyle) { _ in flowPersistence() }) return workflow } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ +// public func thenProceed(with type: F.Type, +// launchStyle: LaunchStyle = .default, +// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow { +// let workflow = Workflow(first) +// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, +// launchStyle: launchStyle) { data in +// guard case.args(let extracted) = data, +// let cast = extracted as? F.WorkflowInput else { return .default } +// +// return flowPersistence(cast) +// }) +// return workflow +// } } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index 9642fdebf..393a8c3e2 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -757,7 +757,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } } - struct FR2: View, FlowRepresentable, Inspectable { // Passthrough + struct FR2: View, FlowRepresentable, Inspectable { typealias WorkflowOutput = AnyWorkflow.PassedArgs var _workflowPointer: AnyFlowRepresentable? private let data: AnyWorkflow.PassedArgs @@ -843,6 +843,68 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) } + func testWorkflowLaunchedFromAnAnyWorkflowCanHavePassthroughFlowRepresentableInTheMiddle() { + struct FR1: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR1 type") } + } + struct FR2: View, FlowRepresentable, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + private let data: AnyWorkflow.PassedArgs + var body: some View { Text("FR2 type") } + + init(with args: AnyWorkflow.PassedArgs) { + self.data = args + } + } + struct FR3: View, FlowRepresentable, Inspectable { + typealias WorkflowInput = String + let str: String + init(with str: String) { + self.str = str + } + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR3 type, \(str)") } + } + struct FR4: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR4 type") } + } + + let wf = Workflow(FR1.self) + .thenProceed(with: FR2.self, flowPersistence: { .default }) + .thenProceed(with: FR3.self, flowPersistence: { _ in .default }) + .thenProceed(with: FR4.self, flowPersistence: { .default }) + + let launcher = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + let expectOnFinish = expectation(description: "OnFinish called") + let expectedArgs = UUID().uuidString + + let expectViewLoaded = ViewHosting.loadView( + launcher + .onFinish { _ in + expectOnFinish.fulfill() + }).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") + XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow(expectedArgs)) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR3.self).text().string(), "FR3 type, \(expectedArgs)") + XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR4.self).text().string(), "FR4 type") + XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) + } + } + } + } + + wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + } + func testIfNoWorkflowItemsThenFatalError() throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? From 686cf28a3ca8d095c672f59d0fbe67a1339df060 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Tue, 9 Nov 2021 10:22:46 -0600 Subject: [PATCH 09/18] Adds support for launching a workflow using an AnyWorkflow with starting/launch args. --- .../Extensions/WorkflowExtensions.swift | 26 ++++---- .../Views/WorkflowLauncher.swift | 18 ++++-- .../SwiftCurrent_SwiftUITests.swift | 62 +++++++++++++++++++ 3 files changed, 89 insertions(+), 17 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 54aff64c2..97a0ff67d 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -132,19 +132,19 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWor - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ -// public func thenProceed(with type: F.Type, -// launchStyle: LaunchStyle = .default, -// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { -// let workflow = Workflow(first) -// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, -// launchStyle: launchStyle) { data in -// guard case.args(let extracted) = data, -// let cast = extracted as? F.WorkflowInput else { return .default } -// -// return flowPersistence(cast) -// }) -// return workflow -// } + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? F.WorkflowInput else { return .default } + + return flowPersistence(cast) + }) + return workflow + } /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index 682902d2c..d24e32317 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -83,7 +83,17 @@ public struct WorkflowLauncher: View { - Parameter workflow: workflow that holds the `WorkflowItem` */ public init(isLaunched: Binding, workflow: Workflow) where Content == AnyWorkflowItem { - self.init(isLaunched: isLaunched, workflow: AnyWorkflow(workflow)) + self.init(isLaunched: isLaunched, startingArgs: .none, workflow: AnyWorkflow(workflow)) + } + + /** + Creates a base for proceeding with a `WorkflowItem`. + - Parameter isLaunched: binding that controls launching the underlying `Workflow`. + - Parameter startingArgs: arguments passed to the first `FlowRepresentable` in the underlying `Workflow`. + - Parameter workflow: workflow that holds the `WorkflowItem` + */ + public init(isLaunched: Binding, startingArgs: AnyWorkflow.PassedArgs, workflow: Workflow) where Content == AnyWorkflowItem { + self.init(isLaunched: isLaunched, startingArgs: startingArgs, workflow: AnyWorkflow(workflow)) } /** @@ -91,16 +101,16 @@ public struct WorkflowLauncher: View { - Parameter isLaunched: binding that controls launching the underlying `Workflow`. - Parameter workflow: workflow that holds the `WorkflowItem` */ - private init(isLaunched: Binding, workflow: AnyWorkflow) where Content == AnyWorkflowItem { + private init(isLaunched: Binding, startingArgs: AnyWorkflow.PassedArgs, workflow: AnyWorkflow) where Content == AnyWorkflowItem { workflow.forEach { assert($0.value.metadata is ExtendedFlowRepresentableMetadata) } _isLaunched = isLaunched - let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: .none) + let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: startingArgs) _model = StateObject(wrappedValue: model) _launcher = StateObject(wrappedValue: Launcher(workflow: workflow, responder: model, - launchArgs: .none)) + launchArgs: startingArgs)) _content = State(wrappedValue: WorkflowLauncher.itemToLaunch(from: workflow)) } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index 393a8c3e2..72430aa54 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -905,6 +905,68 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) } + func testWorkflowLaunchedFromAnAnyWorkflowCanHaveStartingArgs() { + struct FR1: View, FlowRepresentable, Inspectable { + typealias WorkflowOutput = AnyWorkflow.PassedArgs + var _workflowPointer: AnyFlowRepresentable? + var args: AnyWorkflow.PassedArgs + var body: some View { Text("FR1 type, \(args.extractArgs(defaultValue: "") as! String)") } + + init(with args: AnyWorkflow.PassedArgs) { + self.args = args + } + } + struct FR2: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var args: AnyWorkflow.PassedArgs + var body: some View { Text("FR2 type, \(args.extractArgs(defaultValue: "") as! String)") } + + init(with args: AnyWorkflow.PassedArgs) { + self.args = args + } + } + struct FR3: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR3 type") } + } + struct FR4: View, FlowRepresentable, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text("FR4 type") } + } + + let wf = Workflow(FR1.self) + .thenProceed(with: FR2.self, flowPersistence: { _ in .default }) + .thenProceed(with: FR3.self, flowPersistence: { _ in .default }) + .thenProceed(with: FR4.self, flowPersistence: { .default }) + + let expectedArgs = UUID().uuidString + let launcher = WorkflowLauncher(isLaunched: .constant(true), startingArgs: .args(expectedArgs), workflow: wf) + let expectOnFinish = expectation(description: "OnFinish called") + + let expectViewLoaded = ViewHosting.loadView( + launcher + .onFinish { _ in + expectOnFinish.fulfill() + }).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type, \(expectedArgs)") + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow(.args(expectedArgs))) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type, \(expectedArgs)") + XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR3.self).text().string(), "FR3 type") + XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR4.self).text().string(), "FR4 type") + XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) + } + } + } + } + + wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + } + func testIfNoWorkflowItemsThenFatalError() throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? From 1d51a0a5ae57e0b33173f0ddeee01f78f30240e8 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Tue, 9 Nov 2021 14:27:19 -0600 Subject: [PATCH 10/18] Added generic constraint test file for AnyWorkflow launched Workflows. --- ...tendedFlowRepresentableMetadataTests.swift | 2464 +++++++++++++++++ 1 file changed, 2464 insertions(+) create mode 100644 Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift diff --git a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift new file mode 100644 index 000000000..3bfe043c4 --- /dev/null +++ b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift @@ -0,0 +1,2464 @@ +// +// ExtendedFlowRepresentableMetadataTests.swift +// SwiftCurrent +// +// Created by Morgan Zellers on 11/9/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import XCTest +import SwiftUI +import ViewInspector + +import SwiftCurrent + +@testable import SwiftCurrent_SwiftUI + + +//@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +//extension FlowRepresentable { +// var persistence: FlowPersistence? { +// workflow?.first { item in +// item.value.instance === _workflowPointer +// }?.value.metadata.persistence +// } +// +// var presentationType: LaunchStyle.SwiftUI.PresentationType? { +// guard let metadata = workflow?.first(where: { item in +// item.value.instance === _workflowPointer +// })?.value.metadata else { return nil } +// return LaunchStyle.SwiftUI.PresentationType(rawValue: metadata.launchStyle) +// } +//} + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { + override func tearDownWithError() throws { + removeQueuedExpectations() + } + + // MARK: Generic Initializer Tests + + // MARK: Input Type == Never + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNever_PresentationTypeCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).persistence { + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectation, expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgs_PresentationTypeCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + // MARK: Input Type == Concrete Type + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteType_PresentationTypeCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + // MARK: Generic Proceed Tests + + // MARK: Input Type == Never + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { .removedAfterProceeding } + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var body: some View { Text(String(describing: Self.self)) } + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var body: some View { Text(String(describing: Self.self)) } + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } + } + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try view.actualView().inspect { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } + } + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } + } + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + // MARK: Input Type == Concrete Type + func testCreatingMalformedWorkflowWithMismatchingConcreteTypes() throws { + struct FR0: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + + try XCTAssertThrowsFatalError { + _ = WorkflowLauncher(isLaunched: .constant(true)) { + self.thenProceed(with: FR0.self) { + self.thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + } + } + } + + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + } + } + wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { .removedAfterProceeding } + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } + } + } + } + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testProceedingTwiceWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + struct FR1: FlowRepresentable, View, Inspectable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + init(with args: String) { } + } + struct FR2: FlowRepresentable, View, Inspectable { + var _workflowPointer: AnyFlowRepresentable? + var body: some View { Text(String(describing: Self.self)) } + } + let expectedArgs = UUID().uuidString + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } + + func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnView() { + final class FR0: UIViewController, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + } + + final class FR1: UIViewController, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + } + + final class FR2: UIViewController, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + } + + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + } + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(ViewControllerWrapper.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(ViewControllerWrapper.self).actualView().proceedInWorkflow()) + try view.actualView().inspectWrapped { view in + XCTAssertNoThrow(try view.find(ViewControllerWrapper.self)) + } + } + } + wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + } +} From 400001067c21bd61ed4fb711b2b3124a49e22fc3 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Tue, 9 Nov 2021 15:03:23 -0600 Subject: [PATCH 11/18] WIP - Using generic constraints tests to drive out more thenProceeds. --- .../Extensions/WorkflowExtensions.swift | 18 +++++++++++++++ .../Views/WorkflowLauncher.swift | 2 +- ...tendedFlowRepresentableMetadataTests.swift | 23 ++++++++++--------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 97a0ff67d..74f921623 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -24,6 +24,24 @@ extension Workflow where F: FlowRepresentable & View { launchStyle: launchStyle) { _ in flowPersistence() }) } + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + */ + public convenience init(_ type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) { + self.init(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? F.WorkflowInput else { return .default } + + return flowPersistence(cast) + }) + } + /// Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. public func abandon() { AnyWorkflow(self).abandon() diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index d24e32317..24f297163 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -111,7 +111,7 @@ public struct WorkflowLauncher: View { _launcher = StateObject(wrappedValue: Launcher(workflow: workflow, responder: model, launchArgs: startingArgs)) - + _content = State(wrappedValue: WorkflowLauncher.itemToLaunch(from: workflow)) } diff --git a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift index 3bfe043c4..a101c6356 100644 --- a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift @@ -47,9 +47,8 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } + let wf = Workflow(FR1.self, launchStyle: .default, flowPersistence: .removedAfterProceeding) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) @@ -63,9 +62,9 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } + let wf = Workflow(FR1.self, launchStyle: ._swiftUI_navigationLink) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) } @@ -78,12 +77,14 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } } let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).persistence { - defer { expectation.fulfill() } - return .removedAfterProceeding - } + + let wf = Workflow(FR1.self, launchStyle: ._swiftUI_navigationLink) { + defer { expectation.fulfill() } + return .removedAfterProceeding } + + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) } From 3a2a9c455e19a2c3e796e3de73500b827d28ded7 Mon Sep 17 00:00:00 2001 From: Richard Gist Date: Wed, 10 Nov 2021 12:29:51 -0700 Subject: [PATCH 12/18] [launch-with-workflow] - Adding missing init that was causing tests to fail - RAG --- .../Extensions/WorkflowExtensions.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 74f921623..73327a2cb 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -42,6 +42,19 @@ extension Workflow where F: FlowRepresentable & View { }) } + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. + */ + public convenience init(_ type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) where F.WorkflowInput == Never { + self.init(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + } + /// Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. public func abandon() { AnyWorkflow(self).abandon() From f87bbe7c4e48b85131f3443b95c86330122993b5 Mon Sep 17 00:00:00 2001 From: Richard Gist Date: Wed, 10 Nov 2021 14:22:04 -0700 Subject: [PATCH 13/18] [launch-with-workflow] - Fixing linting error - RAG --- Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index 24f297163..f7bf69006 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -111,7 +111,6 @@ public struct WorkflowLauncher: View { _launcher = StateObject(wrappedValue: Launcher(workflow: workflow, responder: model, launchArgs: startingArgs)) - _content = State(wrappedValue: WorkflowLauncher.itemToLaunch(from: workflow)) } From d9e784511b6db1472679c79ff383bf6ed4653d8e Mon Sep 17 00:00:00 2001 From: Richard Gist Date: Wed, 10 Nov 2021 16:41:25 -0700 Subject: [PATCH 14/18] [launch-with-workflow] - Fixed some linting and got some tests passing for the right reasons - RAG --- .../Extensions/WorkflowExtensions.swift | 103 ++++++++++++------ ...tendedFlowRepresentableMetadataTests.swift | 70 ++++++------ 2 files changed, 101 insertions(+), 72 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 73327a2cb..9bd1b6241 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -88,19 +88,19 @@ extension Workflow where F: FlowRepresentable & View { - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ -// public func thenProceed(with type: F.Type, -// launchStyle: LaunchStyle = .default, -// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { -// let workflow = Workflow(first) -// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, -// launchStyle: launchStyle) { data in -// guard case.args(let extracted) = data, -// let cast = extracted as? F.WorkflowInput else { return .default } -// -// return flowPersistence(cast) -// }) -// return workflow -// } + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? F.WorkflowInput else { return .default } + + return flowPersistence(cast) + }) + return workflow + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) @@ -136,6 +136,22 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == Never launchStyle: launchStyle) { _ in flowPersistence() }) return workflow } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) @@ -147,14 +163,14 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWor - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ -// public func thenProceed(with type: F.Type, -// launchStyle: LaunchStyle = .default, -// flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { -// let workflow = Workflow(first) -// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, -// launchStyle: launchStyle) { _ in flowPersistence() }) -// return workflow -// } + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where F.WorkflowInput == AnyWorkflow.PassedArgs { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return workflow + } /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. @@ -200,17 +216,36 @@ extension Workflow where F: FlowRepresentable & View, F.WorkflowOutput == AnyWor - Parameter flowPersistence: a closure returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ -// public func thenProceed(with type: F.Type, -// launchStyle: LaunchStyle = .default, -// flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow { -// let workflow = Workflow(first) -// workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, -// launchStyle: launchStyle) { data in -// guard case.args(let extracted) = data, -// let cast = extracted as? F.WorkflowInput else { return .default } -// -// return flowPersistence(cast) -// }) -// return workflow -// } + public func thenProceed(with type: F.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) -> Workflow { + let workflow = Workflow(first) + workflow.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? F.WorkflowInput else { return .default } + + return flowPersistence(cast) + }) + return workflow + } +} + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension Workflow where F.WorkflowOutput == Never { + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. + - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(ExtendedFlowRepresentableMetadata(flowRepresentableType: type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } } diff --git a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift index a101c6356..9de9400bb 100644 --- a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift @@ -100,15 +100,14 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflow = Workflow(FR1.self) + .thenProceed(with: FR2.self) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) } } wait(for: [expectViewLoaded], timeout: TestConstant.timeout) @@ -123,16 +122,15 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflow = Workflow(FR1.self, flowPersistence: .removedAfterProceeding) + .thenProceed(with: FR2.self) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) } } wait(for: [expectViewLoaded], timeout: TestConstant.timeout) @@ -147,17 +145,15 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } + let workflow = Workflow(FR1.self, flowPersistence: { .removedAfterProceeding }) + .thenProceed(with: FR2.self) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in + XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().persistence, .removedAfterProceeding) + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) } } wait(for: [expectViewLoaded], timeout: TestConstant.timeout) @@ -173,15 +169,13 @@ final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: AnyWorkflow.PassedArgs) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflow = Workflow(FR1.self) + .thenProceed(with: FR2.self) + let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) + try viewUnderTest.actualView().inspect { viewUnderTest in + XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) } } wait(for: [expectViewLoaded], timeout: TestConstant.timeout) From f585656ac63e211172f90ab9ff72f93595e762a4 Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 11 Nov 2021 13:12:09 -0600 Subject: [PATCH 15/18] [launch-with-workflow] - Removed tests for thenProceed functions only needed to test launching with a workflow. Added TODO for the thenProceeds to be removed later. - RAG MZ Co-authored-by: Richard Gist --- .../Extensions/WorkflowExtensions.swift | 2 + ...tendedFlowRepresentableMetadataTests.swift | 2459 ----------------- 2 files changed, 2 insertions(+), 2459 deletions(-) delete mode 100644 Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index 9bd1b6241..d1c41d928 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -60,6 +60,8 @@ extension Workflow where F: FlowRepresentable & View { AnyWorkflow(self).abandon() } + // TODO: Remove the following untested functions when data-driven is more mature + /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. - Parameter type: a reference to the next `FlowRepresentable`'s concrete type in the workflow. diff --git a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift b/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift deleted file mode 100644 index 9de9400bb..000000000 --- a/Tests/SwiftCurrent_SwiftUITests/ExtendedFlowRepresentableMetadataTests.swift +++ /dev/null @@ -1,2459 +0,0 @@ -// -// ExtendedFlowRepresentableMetadataTests.swift -// SwiftCurrent -// -// Created by Morgan Zellers on 11/9/21. -// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. -// - -import XCTest -import SwiftUI -import ViewInspector - -import SwiftCurrent - -@testable import SwiftCurrent_SwiftUI - - -//@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -//extension FlowRepresentable { -// var persistence: FlowPersistence? { -// workflow?.first { item in -// item.value.instance === _workflowPointer -// }?.value.metadata.persistence -// } -// -// var presentationType: LaunchStyle.SwiftUI.PresentationType? { -// guard let metadata = workflow?.first(where: { item in -// item.value.instance === _workflowPointer -// })?.value.metadata else { return nil } -// return LaunchStyle.SwiftUI.PresentationType(rawValue: metadata.launchStyle) -// } -//} - -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -final class ExtendedFlowRepresentableMetadataTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - // MARK: Generic Initializer Tests - - // MARK: Input Type == Never - - func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - - let wf = Workflow(FR1.self, launchStyle: .default, flowPersistence: .removedAfterProceeding) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNever_PresentationTypeCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - - let wf = Workflow(FR1.self, launchStyle: ._swiftUI_navigationLink) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectation = self.expectation(description: "FlowPersistence closure called") - - let wf = Workflow(FR1.self, launchStyle: ._swiftUI_navigationLink) { - defer { expectation.fulfill() } - return .removedAfterProceeding - } - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: wf) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectation, expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let workflow = Workflow(FR1.self) - .thenProceed(with: FR2.self) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let workflow = Workflow(FR1.self, flowPersistence: .removedAfterProceeding) - .thenProceed(with: FR2.self) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let workflow = Workflow(FR1.self, flowPersistence: { .removedAfterProceeding }) - .thenProceed(with: FR2.self) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let workflow = Workflow(FR1.self) - .thenProceed(with: FR2.self) - let workflowView = WorkflowLauncher(isLaunched: .constant(true), workflow: workflow) - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - // MARK: Input Type == AnyWorkflow.PassedArgs - - func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgs_PresentationTypeCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - // MARK: Input Type == Concrete Type - func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteType_PresentationTypeCanBeSetWithAutoclosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - // MARK: Generic Proceed Tests - - // MARK: Input Type == Never - - func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - defer { expectation.fulfill() } - return .removedAfterProceeding - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var body: some View { Text(String(describing: Self.self)) } - var _workflowPointer: AnyFlowRepresentable? - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var body: some View { Text(String(describing: Self.self)) } - var _workflowPointer: AnyFlowRepresentable? - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding - } - } - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - - // MARK: Input Type == AnyWorkflow.PassedArgs - - func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - try view.actualView().inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding - } - } - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - // MARK: Input Type == Concrete Type - func testCreatingMalformedWorkflowWithMismatchingConcreteTypes() throws { - struct FR0: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - - try XCTAssertThrowsFatalError { - _ = WorkflowLauncher(isLaunched: .constant(true)) { - self.thenProceed(with: FR0.self) { - self.thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - } - } - - func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding - } - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = Int - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: Int) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding - } - } - } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testProceedingTwiceWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { - struct FR0: PassthroughFlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - struct FR1: FlowRepresentable, View, Inspectable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - init(with args: String) { } - } - struct FR2: FlowRepresentable, View, Inspectable { - var _workflowPointer: AnyFlowRepresentable? - var body: some View { Text(String(describing: Self.self)) } - } - let expectedArgs = UUID().uuidString - - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } - - func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnView() { - final class FR0: UIViewController, FlowRepresentable { - weak var _workflowPointer: AnyFlowRepresentable? - } - - final class FR1: UIViewController, FlowRepresentable { - weak var _workflowPointer: AnyFlowRepresentable? - } - - final class FR2: UIViewController, FlowRepresentable { - weak var _workflowPointer: AnyFlowRepresentable? - } - - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper.self)) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - } -} From ff16c9968ad3f46a31f203c77eb4b5bd8d13724b Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 11 Nov 2021 13:18:32 -0600 Subject: [PATCH 16/18] [launch-with-workflow] - Adding more descriptive docs - RAG MZ Co-authored-by: Richard Gist --- .../SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index d34b3dff3..378685165 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -80,7 +80,7 @@ public struct WorkflowLauncher: View { /** Creates a base for proceeding with a `WorkflowItem`. - Parameter isLaunched: binding that controls launching the underlying `Workflow`. - - Parameter workflow: workflow that holds the `WorkflowItem` + - Parameter workflow: workflow to be launched; must contain `FlowRepresentable`s of type `View` */ public init(isLaunched: Binding, workflow: Workflow) where Content == AnyWorkflowItem { self.init(isLaunched: isLaunched, startingArgs: .none, workflow: AnyWorkflow(workflow)) @@ -89,18 +89,13 @@ public struct WorkflowLauncher: View { /** Creates a base for proceeding with a `WorkflowItem`. - Parameter isLaunched: binding that controls launching the underlying `Workflow`. - - Parameter startingArgs: arguments passed to the first `FlowRepresentable` in the underlying `Workflow`. - - Parameter workflow: workflow that holds the `WorkflowItem` + - Parameter startingArgs: arguments passed to the first loaded `FlowRepresentable` in the underlying `Workflow`. + - Parameter workflow: workflow to be launched; must contain `FlowRepresentable`s of type `View` */ public init(isLaunched: Binding, startingArgs: AnyWorkflow.PassedArgs, workflow: Workflow) where Content == AnyWorkflowItem { self.init(isLaunched: isLaunched, startingArgs: startingArgs, workflow: AnyWorkflow(workflow)) } - /** - Creates a base for proceeding with a `WorkflowItem`. - - Parameter isLaunched: binding that controls launching the underlying `Workflow`. - - Parameter workflow: workflow that holds the `WorkflowItem` - */ private init(isLaunched: Binding, startingArgs: AnyWorkflow.PassedArgs, workflow: AnyWorkflow) where Content == AnyWorkflowItem { workflow.forEach { assert($0.value.metadata is ExtendedFlowRepresentableMetadata) From 1f62b9bd7fe8dba64c3a5a0259c1265db585975d Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 11 Nov 2021 13:46:32 -0600 Subject: [PATCH 17/18] [launch-with-workflow] - Added TODO to make public Workflow inits private again. - RAG MZ Co-authored-by: Richard Gist --- .../SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift index d1c41d928..7f5e41055 100644 --- a/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift +++ b/Sources/SwiftCurrent_SwiftUI/Extensions/WorkflowExtensions.swift @@ -11,6 +11,11 @@ import SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension Workflow where F: FlowRepresentable & View { + /* + TODO: The below initializers are left public to facilitate testing data-driven workflows. + They should be made private when data driven progress allows. + */ + /** Creates a `Workflow` with a `FlowRepresentable`. - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. From 9a283fd5af788dd815eb8932afba8b6865425bec Mon Sep 17 00:00:00 2001 From: Morgan Zellers Date: Thu, 11 Nov 2021 13:54:54 -0600 Subject: [PATCH 18/18] Taking Richard's whitespace fix Co-authored-by: Richard Gist --- .../FlowRepresentableMetadataConsumerTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift index d6b6dd928..e9de32cfb 100644 --- a/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift +++ b/Tests/SwiftCurrentTests/FlowRepresentableMetadataConsumerTests.swift @@ -10,8 +10,6 @@ import XCTest import SwiftCurrent class FlowRepresentableMetadataConsumerTests: XCTestCase { - - func testOverridingFlowRepresentbleMetadata() { class SpecialConformanceClass { }