diff --git a/ExampleApps/SwiftUIExample/Views/MFAView.swift b/ExampleApps/SwiftUIExample/Views/MFAView.swift index f9c659bf4..4c2138a5e 100644 --- a/ExampleApps/SwiftUIExample/Views/MFAView.swift +++ b/ExampleApps/SwiftUIExample/Views/MFAView.swift @@ -9,9 +9,7 @@ import SwiftUI import SwiftCurrent -struct MFAView: View, FlowRepresentable { - typealias WorkflowOutput = AnyWorkflow.PassedArgs - +struct MFAView: View, PassthroughFlowRepresentable { @State var pushSent = false @State var enteredCode = "" @State var errorMessage: ErrorMessage? @@ -19,11 +17,6 @@ struct MFAView: View, FlowRepresentable { let inspection = Inspection() // ViewInspector weak var _workflowPointer: AnyFlowRepresentable? - private let heldWorkflowData: AnyWorkflow.PassedArgs - init(with data: AnyWorkflow.PassedArgs) { - heldWorkflowData = data - } - var body: some View { VStack(spacing: 30) { if !pushSent { @@ -42,7 +35,7 @@ struct MFAView: View, FlowRepresentable { TextField("Enter Code:", text: $enteredCode) Button("Submit") { if enteredCode == "1234" { - proceedInWorkflow(heldWorkflowData) + proceedInWorkflow() } else { errorMessage = ErrorMessage(message: "Invalid code entered, abandoning workflow.") } diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/GenericConstraintTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/GenericConstraintTests.swift new file mode 100644 index 000000000..dbc307803 --- /dev/null +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/GenericConstraintTests.swift @@ -0,0 +1,1679 @@ +// +// GenericConstraintTests.swift +// SwiftCurrent_UIKitTests +// +// Created by Tyler Thompson on 7/24/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import XCTest + +import SwiftCurrent +import SwiftCurrent_UIKit +final class GenericConstraintTests: XCTestCase { + // MARK: Generic Initializer Tests + + // MARK: Input Type == Never + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { .persistWhenSkipped }) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { .persistWhenSkipped }) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { .persistWhenSkipped }) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + // MARK: Input Type == Concrete Type + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + // MARK: Generic Proceed Tests + + // MARK: Input Type == Never + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + // MARK: Input Type == Concrete Type + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self) + .thenProceed(with: FR1.self, launchStyle: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(expectedArgs) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } +} diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/ModalStyleTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/ModalStyleTests.swift index 69ef251e8..3ee4f59ed 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/ModalStyleTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/ModalStyleTests.swift @@ -29,7 +29,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.fullScreen))) + launchStyle: .modal(.fullScreen))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .fullScreen) @@ -41,7 +41,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.pageSheet))) + launchStyle: .modal(.pageSheet))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .pageSheet) @@ -52,7 +52,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.formSheet))) + launchStyle: .modal(.formSheet))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .formSheet) @@ -63,7 +63,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.currentContext))) + launchStyle: .modal(.currentContext))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .currentContext) @@ -77,7 +77,7 @@ class ModalStyleTests: XCTestCase { print("!!!! \(Date().timeIntervalSince1970) - testShowModalAsCustom - about to launchInto from: \(String(describing: UIApplication.topViewController()))") UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.custom))) + launchStyle: .modal(.custom))) print("!!!! \(Date().timeIntervalSince1970) - testShowModalAsCustom - Completed launchInto") XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -90,7 +90,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.overFullScreen))) + launchStyle: .modal(.overFullScreen))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .overFullScreen) @@ -101,7 +101,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.overCurrentContext))) + launchStyle: .modal(.overCurrentContext))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .overCurrentContext) @@ -112,7 +112,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.popover))) + launchStyle: .modal(.popover))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, .popover) @@ -124,7 +124,7 @@ class ModalStyleTests: XCTestCase { UIApplication.topViewController()? .launchInto(Workflow(TestViewController.self, - presentationType: .modal(.automatic))) + launchStyle: .modal(.automatic))) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) XCTAssertEqual(UIApplication.topViewController()?.modalPresentationStyle, UIViewController().modalPresentationStyle) @@ -235,7 +235,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.fullScreen)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -247,7 +247,7 @@ class ModalStyleTests: XCTestCase { vc.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.pageSheet)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -258,7 +258,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.formSheet)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -269,7 +269,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.currentContext)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -280,7 +280,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.custom)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -291,7 +291,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.overFullScreen)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -302,7 +302,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.overCurrentContext)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -313,7 +313,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.popover)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) @@ -325,7 +325,7 @@ class ModalStyleTests: XCTestCase { RootViewController.standard.loadForTesting() UIApplication.topViewController()? - .launchInto(Workflow(TestViewController.self, presentationType: .navigationStack), + .launchInto(Workflow(TestViewController.self, launchStyle: .navigationStack), withLaunchStyle: .modal(.automatic)) XCTAssertUIViewControllerDisplayed(ofType: TestViewController.self) diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/StoryboardLoadableTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/StoryboardLoadableTests.swift index 7cf3fa89a..59d9abb38 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/StoryboardLoadableTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/StoryboardLoadableTests.swift @@ -90,6 +90,10 @@ class StoryboardLoadableTests: XCTestCase { XCTAssertNoThrow(try ExceptionCatcher.catch { AnyFlowRepresentable(TestInputViewController.self, args: .args("some")) }) + + XCTAssertNoThrow(try ExceptionCatcher.catch { + AnyFlowRepresentable(PassthroughViewController.self, args: .args("some")) + }) } } @@ -116,3 +120,15 @@ class TestInputViewController: UIWorkflowItem, StoryboardLoadable required init?(coder: NSCoder) { nil } } + +// Literally just make sure it compiles.... +class PassthroughViewController: UIViewController, StoryboardLoadable, PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + + static var storyboardId: String { + String(describing: Self.self) + } + static var storyboard: UIStoryboard { + UIStoryboard(name: "TestStoryboard", bundle: Bundle(for: Self.self)) + } +} diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/TestStoryboard.storyboard b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/TestStoryboard.storyboard index 18d6384ce..492a43d40 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/TestStoryboard.storyboard +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/Storyboard/TestStoryboard.storyboard @@ -1,8 +1,8 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -38,6 +38,21 @@ + + + + + + + + + + + + + + + diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerAbandonTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerAbandonTests.swift index f732a8c7f..d3136c375 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerAbandonTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerAbandonTests.swift @@ -203,9 +203,9 @@ class UIKitConsumerAbandonTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self) - .thenPresent(FR4.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) XCTAssertNotNil(UIApplication.topViewController()?.navigationController) @@ -230,9 +230,9 @@ class UIKitConsumerAbandonTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -252,9 +252,9 @@ class UIKitConsumerAbandonTests: XCTestCase { class FR4: TestViewController { } let wf = Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self) let root = UIViewController() root.loadForTesting() @@ -282,9 +282,9 @@ class UIKitConsumerAbandonTests: XCTestCase { class FR4: TestViewController { } let wf = Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self) let root = UIViewController() root.loadForTesting() @@ -314,9 +314,9 @@ class UIKitConsumerAbandonTests: XCTestCase { class FR4: TestViewController { } let wf = Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self, presentationType: .modal) - .thenPresent(FR4.self, presentationType: .modal) + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self, launchStyle: .modal) + .thenProceed(with: FR4.self, launchStyle: .modal) let root = UIViewController() root.loadForTesting() @@ -349,9 +349,9 @@ class UIKitConsumerAbandonTests: XCTestCase { root.loadForTesting() root.launchInto( Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self) - .thenPresent(FR4.self), + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) XCTAssertNotNil(UIApplication.topViewController()?.navigationController) @@ -375,9 +375,9 @@ class UIKitConsumerAbandonTests: XCTestCase { let root = UIViewController() root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self, presentationType: .navigationStack) - .thenPresent(FR4.self, presentationType: .modal), + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self, launchStyle: .navigationStack) + .thenProceed(with: FR4.self, launchStyle: .modal), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -403,9 +403,9 @@ class UIKitConsumerAbandonTests: XCTestCase { root.loadForTesting() root.launchInto( Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self, presentationType: .navigationStack) - .thenPresent(FR4.self, presentationType: .modal), + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self, launchStyle: .navigationStack) + .thenProceed(with: FR4.self, launchStyle: .modal), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -432,9 +432,9 @@ class UIKitConsumerAbandonTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal) - .thenPresent(FR3.self, presentationType: .navigationStack) - .thenPresent(FR4.self, presentationType: .modal), + .thenProceed(with: FR2.self, launchStyle: .modal) + .thenProceed(with: FR3.self, launchStyle: .navigationStack) + .thenProceed(with: FR4.self, launchStyle: .modal), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerLaunchTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerLaunchTests.swift index c3dd4cb13..a7dafd9a2 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerLaunchTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerLaunchTests.swift @@ -108,7 +108,7 @@ class UIKitConsumerLaunchTests: XCTestCase { let nav = UINavigationController() nav.loadForTesting() - nav.launchInto(Workflow(FR1.self, presentationType: .navigationStack), withLaunchStyle: .navigationStack) + nav.launchInto(Workflow(FR1.self, launchStyle: .navigationStack), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) XCTAssertNil(nav.mostRecentlyPresentedViewController) XCTAssertNotNil(UIApplication.topViewController()?.navigationController) @@ -167,7 +167,7 @@ class UIKitConsumerLaunchTests: XCTestCase { controller.loadForTesting() rootController.launchInto(Workflow(ExpectedModal.self) - .thenPresent(ExpectedModalPreferNav.self, presentationType: .navigationStack), + .thenProceed(with: ExpectedModalPreferNav.self, launchStyle: .navigationStack), withLaunchStyle: .modal) RunLoop.current.singlePass() @@ -206,8 +206,8 @@ class UIKitConsumerLaunchTests: XCTestCase { rootController.launchInto( Workflow(ExpectedModal.self) - .thenPresent(ExpectedModalPreferNav.self, - presentationType: .navigationStack), + .thenProceed(with: ExpectedModalPreferNav.self, + launchStyle: .navigationStack), withLaunchStyle: .modal) RunLoop.current.singlePass() @@ -233,7 +233,7 @@ class UIKitConsumerLaunchTests: XCTestCase { firstView.loadForTesting() firstView.present(controller, animated: false) - let workflow = Workflow(ExpectedModal.self, presentationType: .navigationStack) + let workflow = Workflow(ExpectedModal.self, launchStyle: .navigationStack) rootController.launchInto(workflow, withLaunchStyle: .modal) RunLoop.current.singlePass() @@ -260,7 +260,7 @@ class UIKitConsumerLaunchTests: XCTestCase { firstView.loadForTesting() firstView.present(controller, animated: false) - let workflow = Workflow(ExpectedNav.self, presentationType: .navigationStack) + let workflow = Workflow(ExpectedNav.self, launchStyle: .navigationStack) rootController.launchInto(workflow, withLaunchStyle: .modal) RunLoop.current.singlePass() @@ -284,7 +284,7 @@ class UIKitConsumerLaunchTests: XCTestCase { controller.loadForTesting() let workflow = Workflow(TestViewController.self) - .thenPresent(ExpectedModal.self, presentationType: .modal) + .thenProceed(with: ExpectedModal.self, launchStyle: .modal) rootController.launchInto(workflow) @@ -362,7 +362,7 @@ class UIKitConsumerLaunchTests: XCTestCase { controller.loadForTesting() rootController.launchInto(Workflow(ExpectedNav.self) - .thenPresent(ExpectedModal.self, presentationType: .modal)) + .thenProceed(with: ExpectedModal.self, launchStyle: .modal)) XCTAssertUIViewControllerDisplayed(ofType: ExpectedNav.self) XCTAssertEqual(controller.viewControllers.count, 2) diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerPersistenceTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerPersistenceTests.swift index 7441fee3a..14d94d06b 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerPersistenceTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerPersistenceTests.swift @@ -41,8 +41,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .removedAfterProceeding) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -61,8 +61,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -78,8 +78,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .removedAfterProceeding) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -101,11 +101,11 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: { data in - XCTAssertEqual(data as? String, "blah") - return .removedAfterProceeding - }) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: { data in + XCTAssertEqual(data as? String, "blah") + return .removedAfterProceeding + }) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow("blah") XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -127,8 +127,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: .removedAfterProceeding) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -147,8 +147,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -166,8 +166,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .removedAfterProceeding) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: .removedAfterProceeding) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -189,11 +189,11 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: { data in - XCTAssertEqual(data as? String, "blah") - return .removedAfterProceeding - }) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: { data in + XCTAssertEqual(data as? String, "blah") + return .removedAfterProceeding + }) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow("blah") XCTAssertUIViewControllerDisplayed(ofType: FR2.self) @@ -216,8 +216,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -239,8 +239,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -263,8 +263,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -285,8 +285,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self, flowPersistence: .hiddenInitially) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) (UIApplication.topViewController()?.navigationController)?.popViewController(animated: false) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -303,8 +303,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self, flowPersistence: .hiddenInitially) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) (UIApplication.topViewController()?.navigationController)?.popViewController(animated: false) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -323,8 +323,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -345,8 +345,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: { _ in .hiddenInitially }) - .thenPresent(FR3.self), withLaunchStyle: .navigationStack) + .thenProceed(with: FR2.self, flowPersistence: { _ in .hiddenInitially }) + .thenProceed(with: FR3.self), withLaunchStyle: .navigationStack) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow("blah") XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -367,8 +367,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -389,8 +389,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -409,8 +409,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self, flowPersistence: .hiddenInitially) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) UIApplication.topViewController()?.dismiss(animated: false) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -427,8 +427,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: .hiddenInitially) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: .hiddenInitially) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -449,8 +449,8 @@ class UIKitConsumerPersistenceTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self, flowPersistence: { _ in .hiddenInitially }) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self, flowPersistence: { _ in .hiddenInitially }) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow("blah") XCTAssertUIViewControllerDisplayed(ofType: FR3.self) diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerSkipTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerSkipTests.swift index 0801e5414..25df53038 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerSkipTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerSkipTests.swift @@ -49,7 +49,7 @@ class UIKitConsumerSkipTests: XCTestCase { required init?(coder: NSCoder) { nil } } let flow = Workflow(FR1.self) - .thenPresent(FR2.self) + .thenProceed(with: FR2.self) let root = UIViewController() UIApplication.shared.windows.first?.rootViewController = root @@ -71,8 +71,8 @@ class UIKitConsumerSkipTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -91,9 +91,9 @@ class UIKitConsumerSkipTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self)) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) (UIApplication.topViewController() as? FR2)?.proceedInWorkflow(nil) @@ -117,8 +117,8 @@ class UIKitConsumerSkipTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow("worked") @@ -139,8 +139,8 @@ class UIKitConsumerSkipTests: XCTestCase { root.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) XCTAssertUIViewControllerDisplayed(ofType: FR3.self) @@ -176,9 +176,9 @@ class UIKitConsumerSkipTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self), args: obj) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), args: obj) XCTAssertUIViewControllerDisplayed(ofType: FR4.self) XCTAssert((UIApplication.topViewController() as? FR4)?.data as? Obj === obj) } @@ -198,7 +198,7 @@ class UIKitConsumerSkipTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self), args: obj) + .thenProceed(with: FR2.self), args: obj) XCTAssertUIViewControllerDisplayed(ofType: FR2.self) XCTAssert((UIApplication.topViewController() as? FR2)?.data as? Obj === obj) } @@ -219,9 +219,9 @@ class UIKitConsumerSkipTests: XCTestCase { var callbackCalled = false root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self), args: obj) { args in + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), args: obj) { args in callbackCalled = true XCTAssert(args.extractArgs(defaultValue: nil) as? Obj === obj) } @@ -247,8 +247,8 @@ class UIKitConsumerSkipTests: XCTestCase { var callbackCalled = false root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self), args: obj) { args in + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), args: obj) { args in callbackCalled = true XCTAssert(args.extractArgs(defaultValue: nil) as? Obj === obj) } diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerTests.swift index 935361172..11981403d 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerTests.swift @@ -67,9 +67,9 @@ class UIKitConsumerTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -94,9 +94,9 @@ class UIKitConsumerTests: XCTestCase { var callbackCalled = false root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self) - .thenPresent(FR4.self), args: obj) { args in + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self) + .thenProceed(with: FR4.self), args: obj) { args in callbackCalled = true XCTAssert(args.extractArgs(defaultValue: nil) as? Obj === obj) } @@ -131,8 +131,8 @@ class UIKitConsumerTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(MockFlowRepresentable.self) - .thenPresent(FR2.self)) + .thenProceed(with: MockFlowRepresentable.self) + .thenProceed(with: FR2.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) @@ -213,7 +213,7 @@ class UIKitConsumerTests: XCTestCase { required init?(coder: NSCoder) { fatalError() } } - let wf = Workflow(FR1.self, presentationType: .modal) { args in + let wf = Workflow(FR1.self, launchStyle: .modal) { args in expectation.fulfill() XCTAssertEqual(args, expectedArgs) return .persistWhenSkipped @@ -233,7 +233,7 @@ class UIKitConsumerTests: XCTestCase { let expectedArgs = UUID().uuidString class FR1: UIWorkflowItem, FlowRepresentable { } - let wf = Workflow(FR1.self, presentationType: .modal, flowPersistence: .persistWhenSkipped) + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) let root = UIViewController() root.loadForTesting() @@ -252,7 +252,7 @@ class UIKitConsumerTests: XCTestCase { required init?(coder: NSCoder) { fatalError() } } - let wf = Workflow(FR1.self, presentationType: .modal, flowPersistence: .persistWhenSkipped) + let wf = Workflow(FR1.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) let root = UIViewController() root.loadForTesting() @@ -273,7 +273,7 @@ class UIKitConsumerTests: XCTestCase { } let wf = Workflow(FR1.self) - .thenPresent(FR2.self, presentationType: .modal, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FR2.self, launchStyle: .modal, flowPersistence: .persistWhenSkipped) let root = UIViewController() root.loadForTesting() diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerWorkflowChainingTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerWorkflowChainingTests.swift index d24a9ea65..078e28f2f 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerWorkflowChainingTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitConsumerWorkflowChainingTests.swift @@ -44,8 +44,8 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { nav.loadForTesting() root.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -65,7 +65,7 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { class FR1: TestViewController { } class FR2: TestViewController { func launchSecondary() { - let wf = Workflow(FR_1.self, presentationType: .modal) + let wf = Workflow(FR_1.self, launchStyle: .modal) launchInto(wf) { args in self.data = args.extractArgs(defaultValue: nil) wf.abandon(animated: false) @@ -79,8 +79,8 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -114,8 +114,8 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -149,8 +149,8 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { vc.loadForTesting() vc.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self), withLaunchStyle: .modal) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self), withLaunchStyle: .modal) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) @@ -184,8 +184,8 @@ class UIKitConsumerWorkflowChainingTests: XCTestCase { nav.loadForTesting() nav.launchInto(Workflow(FR1.self) - .thenPresent(FR2.self) - .thenPresent(FR3.self)) + .thenProceed(with: FR2.self) + .thenProceed(with: FR3.self)) XCTAssertUIViewControllerDisplayed(ofType: FR1.self) (UIApplication.topViewController() as? FR1)?.proceedInWorkflow(nil) diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitPresenterTests.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitPresenterTests.swift index b0f4b01bd..a3be77497 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitPresenterTests.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/UIKitPresenterTests.swift @@ -18,16 +18,16 @@ class UIKitPresenterTests: XCTestCase { class FR1: TestViewController { } class FR2: TestViewController { } - let wf = Workflow(FR1.self).thenPresent(FR2.self) + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) let rootController = UIViewController() rootController.loadForTesting() let presenter = UIKitPresenter(rootController, launchStyle: .modal) wf.orchestrationResponder = presenter - var fr1 = FR1(with: .none) + let afr = AnyFlowRepresentable(FR1.self, args: .none) let metadata = FlowRepresentableMetadata(FR1.self, launchStyle: ls, flowPersistence: { _ in .default }) - let node = AnyWorkflow.Element(with: _WorkflowItem(metadata: metadata, instance: AnyFlowRepresentable(&fr1))) + let node = AnyWorkflow.Element(with: _WorkflowItem(metadata: metadata, instance: afr)) XCTAssertThrowsFatalError { presenter.proceed(to: node, from: node) diff --git a/ExampleApps/UIKitExample/UIKitExample.xcodeproj/project.pbxproj b/ExampleApps/UIKitExample/UIKitExample.xcodeproj/project.pbxproj index 7061b2b3f..7b440272c 100644 --- a/ExampleApps/UIKitExample/UIKitExample.xcodeproj/project.pbxproj +++ b/ExampleApps/UIKitExample/UIKitExample.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ CA9226EB26406C5B00BD0366 /* StoryboardLoadableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9226EA26406C5B00BD0366 /* StoryboardLoadableTests.swift */; }; CA9226F326406F2000BD0366 /* TestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CA9226F226406F2000BD0366 /* TestStoryboard.storyboard */; }; CA9226F92640724D00BD0366 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = CA9226F82640724D00BD0366 /* ExceptionCatcher */; }; + CA9F44B926ACA3220055020A /* GenericConstraintTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA9F44B826ACA3220055020A /* GenericConstraintTests.swift */; }; CAC1C235256D8DA300DBD0D4 /* TopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1C232256D8DA300DBD0D4 /* TopViewController.swift */; }; CAC1C23D256D8DB100DBD0D4 /* WaitUntil.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1C23C256D8DB100DBD0D4 /* WaitUntil.swift */; }; CAC1C24F256D8F0400DBD0D4 /* ModalStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC1C24E256D8F0400DBD0D4 /* ModalStyleTests.swift */; }; @@ -115,6 +116,7 @@ CA875ABF257353B300243FA4 /* CustomAssertions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomAssertions.swift; sourceTree = ""; }; CA9226EA26406C5B00BD0366 /* StoryboardLoadableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardLoadableTests.swift; sourceTree = ""; }; CA9226F226406F2000BD0366 /* TestStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TestStoryboard.storyboard; sourceTree = ""; }; + CA9F44B826ACA3220055020A /* GenericConstraintTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericConstraintTests.swift; sourceTree = ""; }; CAC1C232256D8DA300DBD0D4 /* TopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopViewController.swift; sourceTree = ""; }; CAC1C23C256D8DB100DBD0D4 /* WaitUntil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitUntil.swift; sourceTree = ""; }; CAC1C24D256D8F0300DBD0D4 /* UIKitConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitConsumerTests.swift; sourceTree = ""; }; @@ -312,6 +314,7 @@ CA357F19263E67DE003D1ACB /* UIKitPresenterTests.swift */, CA50C90E2640E78000FAA8FE /* Storyboard */, CA087B422640E7A100A994D8 /* TestUtilities */, + CA9F44B826ACA3220055020A /* GenericConstraintTests.swift */, ); path = SwiftCurrent_UIKitTests; sourceTree = ""; @@ -502,6 +505,7 @@ CA357F1A263E67DE003D1ACB /* UIKitPresenterTests.swift in Sources */, CAC1C23D256D8DB100DBD0D4 /* WaitUntil.swift in Sources */, D7E1265E264C73BA00B6A712 /* UIKitConsumerLaunchTests.swift in Sources */, + CA9F44B926ACA3220055020A /* GenericConstraintTests.swift in Sources */, CA655AA2265B4F3200F37E47 /* MockOrchestrationResponder.swift in Sources */, D7E12656264C719B00B6A712 /* UIKitConsumerSkipTests.swift in Sources */, ); diff --git a/ExampleApps/UIKitExample/UIKitExample/SetupViewController.swift b/ExampleApps/UIKitExample/UIKitExample/SetupViewController.swift index f970111d7..2dc0d325b 100644 --- a/ExampleApps/UIKitExample/UIKitExample/SetupViewController.swift +++ b/ExampleApps/UIKitExample/UIKitExample/SetupViewController.swift @@ -32,10 +32,10 @@ class SetupViewController: UIViewController { ] launchInto( Workflow(LocationsViewController.self) - .thenPresent(PickupOrDeliveryViewController.self) - .thenPresent(MenuSelectionViewController.self, flowPersistence: .persistWhenSkipped) - .thenPresent(FoodSelectionViewController.self) - .thenPresent(ReviewOrderViewController.self), + .thenProceed(with: PickupOrDeliveryViewController.self) + .thenProceed(with: MenuSelectionViewController.self, flowPersistence: .persistWhenSkipped) + .thenProceed(with: FoodSelectionViewController.self) + .thenProceed(with: ReviewOrderViewController.self), args: locations, withLaunchStyle: .navigationStack) } diff --git a/Sources/SwiftCurrent/Models/Workflow.swift b/Sources/SwiftCurrent/Models/Workflow.swift index ec657d784..e0439d211 100644 --- a/Sources/SwiftCurrent/Models/Workflow.swift +++ b/Sources/SwiftCurrent/Models/Workflow.swift @@ -5,6 +5,7 @@ // Created by Tyler Thompson on 8/26/19. // Copyright © 2021 WWT and Tyler Thompson. All rights reserved. // +// swiftlint:disable file_length import Foundation @@ -296,6 +297,22 @@ extension Workflow where F.WorkflowOutput == Never { return wf } + /** + 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 () -> FlowPersistence) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } + /** 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. @@ -313,6 +330,108 @@ extension Workflow where F.WorkflowOutput == Never { } } +extension Workflow where 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 `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 { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { data in + guard case.args(let extracted) = data, + let cast = extracted as? FR.WorkflowInput else { return .default } + return flowPersistence(cast) + }) + return wf + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { flowPersistence($0) }) + return wf + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } + + /** + 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } + + /** + 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { _ in flowPersistence() }) + return wf + } +} + extension Workflow { /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the `FlowRepresentable.WorkflowInput` of this item. @@ -381,4 +500,20 @@ extension Workflow { launchStyle: launchStyle) { _ in flowPersistence() }) return wf } + + /** + 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: FR.Type, + launchStyle: LaunchStyle = .default, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle) { flowPersistence($0) }) + return wf + } } diff --git a/Sources/SwiftCurrent/Protocols/PassthroughFlowRepresentable.swift b/Sources/SwiftCurrent/Protocols/PassthroughFlowRepresentable.swift new file mode 100644 index 000000000..7605066b6 --- /dev/null +++ b/Sources/SwiftCurrent/Protocols/PassthroughFlowRepresentable.swift @@ -0,0 +1,26 @@ +// +// PassthroughFlowRepresentable.swift +// Workflow +// +// Created by Tyler Thompson on 7/22/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +// swiftlint:disable:next missing_docs +public protocol _PassthroughIdentifiable { } + +/// A `FlowRepresentable` that automatically captures data from the `Workflow` and passes it forward. +public protocol PassthroughFlowRepresentable: FlowRepresentable, _PassthroughIdentifiable where WorkflowInput == AnyWorkflow.PassedArgs, WorkflowOutput == AnyWorkflow.PassedArgs { } + +extension PassthroughFlowRepresentable { + // swiftlint:disable:next missing_docs + public init(with args: WorkflowInput) { self.init() } + + /// Moves forward in the `Workflow`; if at the end, calls the `onFinish` closure used when launching the workflow. + public func proceedInWorkflow() { + guard let pointer = _workflowPointer else { + fatalError("Cannot proceed in workflow, no workflow pointer found.") + } + pointer.proceedInWorkflowStorage?(pointer.argsHolder) + } +} diff --git a/Sources/SwiftCurrent/TypeErased/AnyFlowRepresentable.swift b/Sources/SwiftCurrent/TypeErased/AnyFlowRepresentable.swift index 00305ec86..f9c48f6fa 100644 --- a/Sources/SwiftCurrent/TypeErased/AnyFlowRepresentable.swift +++ b/Sources/SwiftCurrent/TypeErased/AnyFlowRepresentable.swift @@ -19,6 +19,9 @@ open class AnyFlowRepresentable { _storage.underlyingInstance } + var isPassthrough = false + var argsHolder: AnyWorkflow.PassedArgs + var workflow: AnyWorkflow? { get { _storage.workflow @@ -45,17 +48,13 @@ open class AnyFlowRepresentable { fileprivate var _storage: AnyFlowRepresentableStorageBase - init(_ instance: inout FR) { - _storage = AnyFlowRepresentableStorage(&instance) - _storage._workflowPointer = self - } - /** Creates an erased `FlowRepresentable` by using its initializer - Parameter type: The `FlowRepresentable` type to create an instance of - Parameter args: The `AnyWorkflow.PassedArgs` to create the instance with. This ends up being cast into the `FlowRepresentable.WorkflowInput`. */ public init(_ type: FR.Type, args: AnyWorkflow.PassedArgs) { + argsHolder = args switch args { case _ where FR.WorkflowInput.self == Never.self: var instance = FR._factory(FR.self) @@ -71,6 +70,7 @@ open class AnyFlowRepresentable { default: fatalError("No arguments were passed to representable: \(FR.self), but it expected: \(FR.WorkflowInput.self)") } _storage._workflowPointer = self + isPassthrough = FR.self is _PassthroughIdentifiable.Type } func shouldLoad() -> Bool { _storage.shouldLoad() } diff --git a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift index 258714a4b..ba08a73f8 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift @@ -143,6 +143,15 @@ extension ModifiedWorkflowView where Args == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, item: item) } + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward. + - Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. + */ + public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == Never { + ModifiedWorkflowView(self, item: item) + } + /** Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward. - Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow. @@ -172,4 +181,13 @@ extension ModifiedWorkflowView { public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, item: item) } + + /** + Adds an item to the workflow; enforces the `FlowRepresentable.WorkflowOutput` of the previous item matches the args that will be passed forward. + - Parameter workflowItem: a `WorkflowItem` that holds onto the next `FlowRepresentable` in the workflow. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. + */ + public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == Never { + ModifiedWorkflowView(self, item: item) + } } diff --git a/Sources/SwiftCurrent_UIKit/Extensions/DeprecatedWorkflowUIKitAdditions.swift b/Sources/SwiftCurrent_UIKit/Extensions/DeprecatedWorkflowUIKitAdditions.swift new file mode 100644 index 000000000..35f779904 --- /dev/null +++ b/Sources/SwiftCurrent_UIKit/Extensions/DeprecatedWorkflowUIKitAdditions.swift @@ -0,0 +1,185 @@ +// swiftlint:disable:this file_name +// Reason: The file name reflects the contents of the file. +// +// DeprecatedWorkflowUIKitAdditions.swift +// Workflow +// +// Created by Tyler Thompson on 5/5/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import Foundation +import SwiftCurrent + +// UI friendly terms for creating a workflow. +extension Workflow { + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this workflow. + - Parameter flowPersistence: the `FlowPersistence` representing how this item in the workflow should persist. + */ + @available(*, deprecated, renamed: "init(_:launchStyle:flowPersistence:)") + public convenience init(_ type: F.Type, + presentationType: LaunchStyle.PresentationType, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) { + self.init(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + } + + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this workflow. + - Parameter flowPersistence: a closure taking in the `FlowRepresentable.WorkflowInput` and returning a `FlowPersistence` representing how this item in the workflow should persist. + */ + @available(*, deprecated, renamed: "init(_:launchStyle:flowPersistence:)") + public convenience init(_ type: F.Type, + presentationType: LaunchStyle.PresentationType, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) { + self.init(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { data in + guard case.args(let extracted) = data, + let cast = extracted as? F.WorkflowInput else { return .default } + return flowPersistence(cast) + }) + } + + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable 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. + */ + @available(*, deprecated, renamed: "init(_:launchStyle:flowPersistence:)") + public convenience init(_ type: F.Type, + presentationType: LaunchStyle.PresentationType, + flowPersistence: @autoclosure @escaping () -> FlowPersistence) where F.WorkflowInput == Never { + self.init(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + } + + /** + Creates a `Workflow` with a `FlowRepresentable`. + - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. + - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable 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. + */ + @available(*, deprecated, renamed: "init(_:launchStyle:flowPersistence:)") + public convenience init(_ type: F.Type, + presentationType: LaunchStyle.PresentationType, + flowPersistence: @autoclosure @escaping () -> FlowPersistence) where F.WorkflowInput == AnyWorkflow.PassedArgs { + self.init(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + } +} + +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 presentationType: the `LaunchStyle.PresentationType` 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. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + return wf + } + + /** + 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 presentationType: the `LaunchStyle.PresentationType` 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. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + return wf + } +} + +extension 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 presentationType: the `LaunchStyle.PresentationType` 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. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where F.WorkflowOutput == FR.WorkflowInput { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + return wf + } + + /** + 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure taking in the `FlowRepresentable.WorkflowInput` and returning a `FlowPersistence` representing how this item in the workflow should persist. + - Returns: a new workflow with the additional `FlowRepresentable` item. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowOutput == FR.WorkflowInput { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { data in + guard case.args(let extracted) = data, + let cast = extracted as? FR.WorkflowInput else { return .default } + return flowPersistence(cast) + }) + return wf + } + + /** + 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 presentationType: the `LaunchStyle.PresentationType` 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. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + return wf + } + + /** + 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 presentationType: the `LaunchStyle.PresentationType` 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. + */ + @available(*, deprecated, renamed: "thenProceed(with:launchStyle:flowPersistence:)") + public func thenPresent(_ type: FR.Type, + presentationType: LaunchStyle.PresentationType = .default, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + return wf + } +} diff --git a/Sources/SwiftCurrent_UIKit/Extensions/WorkflowUIKitAdditions.swift b/Sources/SwiftCurrent_UIKit/Extensions/WorkflowUIKitAdditions.swift index 5263c6486..68fa74de9 100644 --- a/Sources/SwiftCurrent_UIKit/Extensions/WorkflowUIKitAdditions.swift +++ b/Sources/SwiftCurrent_UIKit/Extensions/WorkflowUIKitAdditions.swift @@ -11,33 +11,68 @@ import Foundation import SwiftCurrent -// UI friendly terms for creating a workflow. +extension Workflow { + /** + Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. + - Parameter animated: a boolean indicating whether abandoning the workflow should be animated. + - Parameter onFinish: a callback after the workflow has been abandoned. + - Important: In order to dismiss UIKit views the workflow must have an `OrchestrationResponder` that is a `UIKitPresenter`. + */ + public func abandon(animated: Bool = true, onFinish:(() -> Void)? = nil) { + AnyWorkflow(self).abandon(animated: animated, onFinish: onFinish) + } +} + +extension AnyWorkflow { + /** + Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. + - Parameter animated: a boolean indicating whether abandoning the workflow should be animated. + - Parameter onFinish: a callback after the workflow has been abandoned. + - Important: In order to dismiss UIKit views the workflow must have an `OrchestrationResponder` that is a `UIKitPresenter`. + */ + public func abandon(animated: Bool = true, onFinish:(() -> Void)? = nil) { + if let presenter = orchestrationResponder as? UIKitPresenter { + presenter.abandon(self, animated: animated) { [weak self] in + self?._abandon() + onFinish?() + } + } else if let responder = orchestrationResponder { + responder.abandon(self) { [weak self] in + self?._abandon() + onFinish?() + } + } else { + _abandon() + } + } +} + extension Workflow { /** Creates a `Workflow` with a `FlowRepresentable`. - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. - - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this workflow. - - Parameter flowPersistence: the `FlowPersistence` representing how this item in the workflow should persist. + - 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, - presentationType: LaunchStyle.PresentationType, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) { self.init(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) } /** Creates a `Workflow` with a `FlowRepresentable`. - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. - - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this workflow. - - Parameter flowPersistence: a closure taking in the `FlowRepresentable.WorkflowInput` and returning a `FlowPersistence` representing how this item in the workflow should persist. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure taking in the generic type from the `FlowRepresentable` and returning a `FlowPersistence` representing how this item in the workflow should persist. */ public convenience init(_ type: F.Type, - presentationType: LaunchStyle.PresentationType, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) { self.init(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { data in - guard case.args(let extracted) = data, + launchStyle: launchStyle.rawValue) { data in + guard case .args(let extracted) = data, let cast = extracted as? F.WorkflowInput else { return .default } return flowPersistence(cast) }) @@ -46,166 +81,248 @@ extension Workflow { /** Creates a `Workflow` with a `FlowRepresentable`. - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. - - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this 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, - presentationType: LaunchStyle.PresentationType, - flowPersistence: @autoclosure @escaping () -> FlowPersistence) where F.WorkflowInput == Never { + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping () -> FlowPersistence) where F.WorkflowInput == Never { self.init(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) } /** Creates a `Workflow` with a `FlowRepresentable`. - Parameter type: a reference to the first `FlowRepresentable`'s concrete type in the workflow. - - Parameter presentationType: the `LaunchStyle.PresentationType` the flow representable should use while it's part of this 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, - presentationType: LaunchStyle.PresentationType, - flowPersistence: @autoclosure @escaping () -> FlowPersistence) where F.WorkflowInput == AnyWorkflow.PassedArgs { + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping (F.WorkflowInput) -> FlowPersistence) where F.WorkflowInput == AnyWorkflow.PassedArgs { self.init(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { flowPersistence($0) }) } } -extension Workflow { +extension Workflow where F.WorkflowOutput == Never { /** - Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. - - Parameter animated: a boolean indicating whether abandoning the workflow should be animated. - - Parameter onFinish: a callback after the workflow has been abandoned. - - Important: In order to dismiss UIKit views the workflow must have an `OrchestrationResponder` that is a `UIKitPresenter`. + 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 abandon(animated: Bool = true, onFinish:(() -> Void)? = nil) { - AnyWorkflow(self).abandon(animated: animated, onFinish: onFinish) + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) } -} -extension AnyWorkflow { /** - Called when the workflow should be terminated, and the app should return to the point before the workflow was launched. - - Parameter animated: a boolean indicating whether abandoning the workflow should be animated. - - Parameter onFinish: a callback after the workflow has been abandoned. - - Important: In order to dismiss UIKit views the workflow must have an `OrchestrationResponder` that is a `UIKitPresenter`. + 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 abandon(animated: Bool = true, onFinish:(() -> Void)? = nil) { - if let presenter = orchestrationResponder as? UIKitPresenter { - presenter.abandon(self, animated: animated) { [weak self] in - self?._abandon() - onFinish?() - } - } else if let responder = orchestrationResponder { - responder.abandon(self) { [weak self] in - self?._abandon() - onFinish?() - } - } else { - _abandon() - } + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where FR.WorkflowInput == Never { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) } -} -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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this 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 thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, - flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { let wf = Workflow(first) wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) return wf } +} +extension Workflow where 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this 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 thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow { + let wf = Workflow(first) + wf.append(FlowRepresentableMetadata(type, + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) + return wf + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) + } + + /** + 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 taking in the generic type from the `FlowRepresentable.WorkflowInput` and 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { let wf = Workflow(first) wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) return wf } + + /** + 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) + } + + /** + 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping () -> FlowPersistence) -> Workflow where FR.WorkflowInput == Never { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) + } } extension 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this 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 thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where F.WorkflowOutput == FR.WorkflowInput { let wf = Workflow(first) wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) return wf } /** 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this workflow. - - Parameter flowPersistence: a closure taking in the `FlowRepresentable.WorkflowInput` and returning a `FlowPersistence` representing how this item in the workflow should persist. + - Parameter launchStyle: the `LaunchStyle` the `FlowRepresentable` should use while it's part of this workflow. + - Parameter flowPersistence: a closure taking in the generic type from the `FlowRepresentable.WorkflowInput` and returning a `FlowPersistence` representing how this item in the workflow should persist. - Returns: a new workflow with the additional `FlowRepresentable` item. */ - public func thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where F.WorkflowOutput == FR.WorkflowInput { - let wf = Workflow(first) - wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { data in - guard case.args(let extracted) = data, - let cast = extracted as? FR.WorkflowInput else { return .default } - return flowPersistence(cast) - }) - return wf + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) } /** 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this workflow. - - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + - 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 thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == Never { let wf = Workflow(first) wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) return wf } /** 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 presentationType: the `LaunchStyle.PresentationType` the `FlowRepresentable` should use while it's part of this workflow. - - Parameter flowPersistence: a `FlowPersistence` representing how this item in the workflow should persist. + - 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 thenPresent(_ type: FR.Type, - presentationType: LaunchStyle.PresentationType = .default, + public func thenProceed(with type: FR.Type, + launchStyle: LaunchStyle.PresentationType, flowPersistence: @escaping @autoclosure () -> FlowPersistence = .default) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { let wf = Workflow(first) wf.append(FlowRepresentableMetadata(type, - launchStyle: presentationType.rawValue) { _ in flowPersistence() }) + launchStyle: launchStyle.rawValue) { _ in flowPersistence() }) return wf } + + /** + 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: FR.Type, + launchStyle: LaunchStyle.PresentationType, + flowPersistence: @escaping (FR.WorkflowInput) -> FlowPersistence) -> Workflow where FR.WorkflowInput == AnyWorkflow.PassedArgs { + thenProceed(with: type, + launchStyle: launchStyle.rawValue, + flowPersistence: flowPersistence) + } } diff --git a/Sources/SwiftCurrent_UIKit/StoryboardLoadable.swift b/Sources/SwiftCurrent_UIKit/StoryboardLoadable.swift index 4599e851a..e949ad92d 100644 --- a/Sources/SwiftCurrent_UIKit/StoryboardLoadable.swift +++ b/Sources/SwiftCurrent_UIKit/StoryboardLoadable.swift @@ -70,6 +70,15 @@ extension StoryboardLoadable { } } +@available(iOS 13.0, *) +extension PassthroughFlowRepresentable where Self: StoryboardLoadable { + /// :nodoc: **WARNING: This will throw a fatal error.** Just a default implementation of the required `FlowRepresentable` initializer meant to satisfy the protocol requirements. + public init(with args: WorkflowInput) { self.init() } + + // swiftlint:disable:next missing_docs + public init?(coder: NSCoder, with args: WorkflowInput) { self.init(coder: coder) } +} + @available(iOS 13.0, *) extension StoryboardLoadable where WorkflowInput == Never { // No public docs necessary, this cannot be called. diff --git a/SwiftCurrent.podspec b/SwiftCurrent.podspec index 8a1c6f940..ae60fa574 100644 --- a/SwiftCurrent.podspec +++ b/SwiftCurrent.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SwiftCurrent' - s.version = '4.1.5' + s.version = '4.2.0' s.summary = 'A library for complex workflows in Swift' s.description = <<-DESC SwiftCurrent is a library that lets you easily manage journeys through your Swift application. diff --git a/Tests/SwiftCurrentTests/FlowPersistenceTests.swift b/Tests/SwiftCurrentTests/FlowPersistenceTests.swift index aacb3674c..8d8ae4ec7 100644 --- a/Tests/SwiftCurrentTests/FlowPersistenceTests.swift +++ b/Tests/SwiftCurrentTests/FlowPersistenceTests.swift @@ -1,8 +1,9 @@ // // FlowPersistenceTests.swift -// +// SwiftCurrentTests // // Created by Tyler Thompson on 11/26/20. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. // import Foundation diff --git a/Tests/SwiftCurrentTests/GenericConstraintTests.swift b/Tests/SwiftCurrentTests/GenericConstraintTests.swift new file mode 100644 index 000000000..496a4967d --- /dev/null +++ b/Tests/SwiftCurrentTests/GenericConstraintTests.swift @@ -0,0 +1,1600 @@ +// +// GenericConstraintTests.swift +// SwiftCurrentTests +// +// Created by Tyler Thompson on 7/24/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import XCTest + +import SwiftCurrent + +final class GenericConstraintTests: XCTestCase { + // MARK: Generic Initializer Tests + + // MARK: Input Type == Never + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, flowPersistence: { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { .persistWhenSkipped }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { .persistWhenSkipped }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { .persistWhenSkipped }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + // MARK: Input Type == Concrete Type + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: .persistWhenSkipped).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + + XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.value.instance?.underlyingInstance is FR2) + } + + // MARK: Generic Proceed Tests + + // MARK: Input Type == Never + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + + // MARK: Input Type == AnyWorkflow.PassedArgs + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + // MARK: Input Type == Concrete Type + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let expectation = self.expectation(description: "FlowPersistence closure called") + let wf = Workflow(FR0.self).thenProceed(with: FR1.self, flowPersistence: { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + wait(for: [expectation], timeout: 0.1) + XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow() + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = Int + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: Int) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(1) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + } + + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: { _ in .persistWhenSkipped }) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow("") + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } + + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() throws { + struct FR0: PassthroughFlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + } + struct FR1: FlowRepresentable { + typealias WorkflowOutput = String + var _workflowPointer: AnyFlowRepresentable? + init(with args: String) { } + } + struct FR2: FlowRepresentable { + var _workflowPointer: AnyFlowRepresentable? + init(with args: AnyWorkflow.PassedArgs) { } + } + let expectedArgs = UUID().uuidString + + let wf = Workflow(FR0.self).thenProceed(with: FR1.self).thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) + + wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), args: expectedArgs) + try XCTUnwrap(wf.first?.value.instance?.underlyingInstance as? FR0).proceedInWorkflow() + + try XCTUnwrap(wf.first?.next?.value.instance?.underlyingInstance as? FR1).proceedInWorkflow(expectedArgs) + XCTAssert(wf.first?.next?.next?.value.instance?.underlyingInstance is FR2) + XCTAssertEqual(wf.first?.next?.next?.value.metadata.persistence, .persistWhenSkipped) + } +} diff --git a/Tests/SwiftCurrentTests/WorkflowConsumerTests.swift b/Tests/SwiftCurrentTests/WorkflowConsumerTests.swift index 5f61e593d..55d49f1ca 100644 --- a/Tests/SwiftCurrentTests/WorkflowConsumerTests.swift +++ b/Tests/SwiftCurrentTests/WorkflowConsumerTests.swift @@ -209,28 +209,6 @@ class WorkflowConsumerTests: XCTestCase { XCTAssert(mockOrchestrationResponder.allTos[3].value.instance?.underlyingInstance is FR4, "Expected orchestration responder to proceed to FR4, but was: \(String(describing: mockOrchestrationResponder.allTos[3].value.instance?.underlyingInstance))") } - func testWorkflowCanBeInitialized_WithFlowPersistenceClosure() { - struct FR1: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - init(with name: String) { } - } - let expectation = self.expectation(description: "FlowPersistence closure called") - let expectedArgs = UUID().uuidString - - let wf = Workflow(FR1.self) { args in - XCTAssertEqual(args, expectedArgs) - expectation.fulfill() - return .persistWhenSkipped - } - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), - args: expectedArgs) - - wait(for: [expectation], timeout: 0.1) - - XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) - } - func testWorkflowThrowsFatalError_WhenLaunchedWithWrongArgumentType() { struct FR1: FlowRepresentable { var _workflowPointer: AnyFlowRepresentable? @@ -244,115 +222,6 @@ class WorkflowConsumerTests: XCTestCase { args: 1) } } - - func testWorkflowCanBeInitialized_WithFlowPersistenceClosure_WhenTheFirstItemHasNoInput() { - struct FR1: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - } - let expectation = self.expectation(description: "FlowPersistence closure called") - let expectedArgs = UUID().uuidString - - let wf = Workflow(FR1.self) { - expectation.fulfill() - return .persistWhenSkipped - } - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), - args: expectedArgs) - - wait(for: [expectation], timeout: 0.1) - - XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) - } - - func testWorkflowCanBeInitialized_WithFlowPersistenceClosure_WhenTheFirstItemHasPassedArgsInput() { - struct FR1: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - init(with args: AnyWorkflow.PassedArgs) { } - } - let expectation = self.expectation(description: "FlowPersistence closure called") - let expectedArgs = UUID().uuidString - - let wf = Workflow(FR1.self) { args in - expectation.fulfill() - XCTAssertEqual(args.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .persistWhenSkipped - } - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder(), - args: expectedArgs) - - wait(for: [expectation], timeout: 0.1) - - XCTAssertEqual(wf.first?.value.metadata.persistence, .persistWhenSkipped) - } - - func testWorkflowCanProceed_WithFlowPersistenceAutoClosure_WhenTheFirstItemHasPassedArgsInput() { - struct FR1: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - } - struct FR2: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - init(with args: AnyWorkflow.PassedArgs) { } - } - let wf = Workflow(FR1.self) - .thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder()) - - (wf.first?.value.instance?.underlyingInstance as? FR1)?.proceedInWorkflow() - - XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) - } - - func testWorkflowCanProceed_WithFlowPersistenceClosure() { - struct FR1: FlowRepresentable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - } - struct FR2: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - init(with name: String) { } - } - let expectation = self.expectation(description: "FlowPersistence closure called") - let expectedArgs = UUID().uuidString - - let wf = Workflow(FR1.self) - .thenProceed(with: FR2.self) { args in - XCTAssertEqual(args, expectedArgs) - expectation.fulfill() - return .persistWhenSkipped - } - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder()) - - (wf.first?.value.instance?.underlyingInstance as? FR1)?.proceedInWorkflow(expectedArgs) - - wait(for: [expectation], timeout: 0.1) - - XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) - } - - func testWorkflowCanProceed_WithFlowPersistenceAutoClosure_WhenRepresentableHasInputOfPassedArgs() { - struct FR1: FlowRepresentable { - typealias WorkflowOutput = String - var _workflowPointer: AnyFlowRepresentable? - } - struct FR2: FlowRepresentable { - var _workflowPointer: AnyFlowRepresentable? - init(with name: AnyWorkflow.PassedArgs) { } - } - let expectedArgs = UUID().uuidString - - let wf = Workflow(FR1.self) - .thenProceed(with: FR2.self, flowPersistence: .persistWhenSkipped) - - wf.launch(withOrchestrationResponder: MockOrchestrationResponder()) - - (wf.first?.value.instance?.underlyingInstance as? FR1)?.proceedInWorkflow(expectedArgs) - - XCTAssertEqual(wf.first?.next?.value.metadata.persistence, .persistWhenSkipped) - } } extension WorkflowConsumerTests { diff --git a/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift b/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift new file mode 100644 index 000000000..3f9482880 --- /dev/null +++ b/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift @@ -0,0 +1,2097 @@ +// +// GenericConstraintTests.swift +// SwiftCurrent_SwiftUITests +// +// Created by Tyler Thompson on 7/24/21. +// Copyright © 2021 WWT and Tyler Thompson. All rights reserved. +// + +import XCTest +import SwiftUI +import ViewInspector + +import SwiftCurrent + +@testable import SwiftCurrent_SwiftUI + +extension FlowRepresentable { + var persistence: FlowPersistence? { + workflow?.first { item in + item.value.instance === _workflowPointer + }?.value.metadata.persistence + } +} + +@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +final class GenericConstraintTests: XCTestCase { + // 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectation, expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { .persistWhenSkipped }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { .persistWhenSkipped }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { .persistWhenSkipped }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + + // 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded, expectation], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + // 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded, expectation], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + return .persistWhenSkipped + }) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + // 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence { + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded, expectation], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { .persistWhenSkipped }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { _ in .persistWhenSkipped }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + + // 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded, expectation], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }).thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + // MARK: Input Type == Concrete Type + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded, expectation], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { .persistWhenSkipped }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { _ in .persistWhenSkipped }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { + XCTAssertEqual($0, 1) + return .persistWhenSkipped + }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence { _ in .persistWhenSkipped }) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } + + 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: WorkflowItem(FR0.self)) + .thenProceed(with: WorkflowItem(FR1.self)) + .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) + + let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) + + XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .persistWhenSkipped) + } + wait(for: [expectViewLoaded], timeout: 0.5) + } +} diff --git a/Tests/UIKitExampleTests/SetupViewControllerTests.swift b/Tests/UIKitExampleTests/SetupViewControllerTests.swift index 644935c01..f00e861ef 100644 --- a/Tests/UIKitExampleTests/SetupViewControllerTests.swift +++ b/Tests/UIKitExampleTests/SetupViewControllerTests.swift @@ -25,10 +25,10 @@ class SetupViewControllerTests: XCTestCase { testViewController.launchWorkflowButton?.simulateTouch() XCTAssertWorkflowLaunched(listener: listener, workflow: Workflow(LocationsViewController.self) - .thenPresent(PickupOrDeliveryViewController.self) - .thenPresent(MenuSelectionViewController.self) - .thenPresent(FoodSelectionViewController.self) - .thenPresent(ReviewOrderViewController.self), + .thenProceed(with: PickupOrDeliveryViewController.self) + .thenProceed(with: MenuSelectionViewController.self) + .thenProceed(with: FoodSelectionViewController.self) + .thenProceed(with: ReviewOrderViewController.self), passedArgs: [ .args([Location]()), .args(Order(location: nil)),