From 1f459b15d45060abdca1da04ef956440ba081285 Mon Sep 17 00:00:00 2001 From: Tyler Thompson Date: Thu, 22 Jul 2021 14:23:32 -0600 Subject: [PATCH] [die-anyview-die] - We have added compiler safety and painstakingly made sure we aren't lying to our users! - MOB --- ExampleApps/SwiftUIExample/README.md | 4 +- .../Views/ChangePasswordViewTests.swift | 2 +- .../Views/ContentViewTests.swift | 18 +++--- .../Views/MapFeatureOnboardingViewTests.swift | 2 +- .../ProfileFeatureOnboardingViewTests.swift | 2 +- .../QRScannerFeatureOnboardingViewTests.swift | 2 +- .../SwiftUIExample/Views/ContentView.swift | 6 +- .../Profile/AccountInformationView.swift | 4 +- README.md | 2 +- .../Views/ModifiedWorkflowView.swift | 58 +++++++++---------- ...kflowView.swift => WorkflowLauncher.swift} | 41 +++++-------- .../SwiftCurrent_SwiftUI/WorkflowItem.swift | 2 +- .../PersistenceTests.swift | 26 ++++----- .../SwiftCurrent_SwiftUITests/SkipTests.swift | 10 ++-- .../SwiftCurrent_SwiftUITests.swift | 58 ++++++++----------- .../ViewInspector/InspectableExtensions.swift | 3 - wiki/Getting-Started-SwiftUI.md | 12 ++-- 17 files changed, 115 insertions(+), 137 deletions(-) rename Sources/SwiftCurrent_SwiftUI/Views/{WorkflowView.swift => WorkflowLauncher.swift} (81%) diff --git a/ExampleApps/SwiftUIExample/README.md b/ExampleApps/SwiftUIExample/README.md index cc758c74b..07cd19eb7 100644 --- a/ExampleApps/SwiftUIExample/README.md +++ b/ExampleApps/SwiftUIExample/README.md @@ -9,10 +9,10 @@ The project is designed to give you an idea of SwiftCurrent functionality while Here is a list of things that are interesting to look at from a SwiftCurrent perspective. ### ContentView -This starting view shows the creation of the 3 main `WorkflowView`s and how SwiftCurrent can be helpful in a `TabView` setting. +This starting view shows the creation of the 3 main workflows and how SwiftCurrent can be helpful in a `TabView` setting. ### FeatureOnboardingViews Each feature has an onboarding view. Look at these to see examples of single launch screens that can be placed at the start of a feature flow. ### AccountInformationView -This view launches a `WorkflowView` that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security. +This view launches a workflow that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security. diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift index e536dd97f..c15a77d54 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift @@ -27,7 +27,7 @@ final class ChangePasswordViewTests: XCTestCase { func testChangePasswordProceeds_IfAllInformationIsCorrect() throws { let currentPassword = UUID().uuidString let onFinish = expectation(description: "onFinish called") - let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true), startingArgs: currentPassword) + let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword) .thenProceed(with: WorkflowItem(ChangePasswordView.self)) .onFinish { _ in onFinish.fulfill() }).inspection.inspect { view in XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput(currentPassword)) diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift index 7a5fd3a82..84d00369b 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift @@ -41,17 +41,17 @@ final class ContentViewTests: XCTestCase { XCTAssertNotNil(wf2) XCTAssertNotNil(wf3) wait(for: [ - ViewHosting.loadView(wf1)?.inspection.inspect { workflowView in - XCTAssertNoThrow(try workflowView.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try workflowView.find(MapFeatureView.self)) + ViewHosting.loadView(wf1)?.inspection.inspect { WorkflowLauncher in + XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureView.self)) }, - ViewHosting.loadView(wf2)?.inspection.inspect { workflowView in - XCTAssertNoThrow(try workflowView.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try workflowView.find(QRScannerFeatureView.self)) + ViewHosting.loadView(wf2)?.inspection.inspect { WorkflowLauncher in + XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureView.self)) }, - ViewHosting.loadView(wf3)?.inspection.inspect { workflowView in - XCTAssertNoThrow(try workflowView.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try workflowView.find(ProfileFeatureView.self)) + ViewHosting.loadView(wf3)?.inspection.inspect { WorkflowLauncher in + XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow()) + XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureView.self)) } ].compactMap { $0 }, timeout: TestConstant.timeout) } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift index 84dc61801..0748741b3 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift @@ -25,7 +25,7 @@ final class MapFeatureOnboardingViewTests: XCTestCase { defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true)) + let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(MapFeatureOnboardingView.self)) .onFinish { _ in workflowFinished.fulfill() diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift index a24cbf6df..617fe4346 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift @@ -25,7 +25,7 @@ final class ProfileFeatureOnboardingViewTests: XCTestCase { defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true)) + let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(ProfileFeatureOnboardingView.self)) .onFinish { _ in workflowFinished.fulfill() diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift index 559a704cc..44e2ba15d 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift @@ -25,7 +25,7 @@ final class QRScannerFeatureOnboardingViewTests: XCTestCase { defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true)) + let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(QRScannerFeatureOnboardingView.self)) .onFinish { _ in workflowFinished.fulfill() diff --git a/ExampleApps/SwiftUIExample/Views/ContentView.swift b/ExampleApps/SwiftUIExample/Views/ContentView.swift index 8e8e0e646..fa32a2e3a 100644 --- a/ExampleApps/SwiftUIExample/Views/ContentView.swift +++ b/ExampleApps/SwiftUIExample/Views/ContentView.swift @@ -20,7 +20,7 @@ struct ContentView: View { var body: some View { TabView(selection: $selectedTab) { // NOTE: Using constant here guarantees the workflow cannot abandon, it stays launched forever. - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(MapFeatureOnboardingView.self)) .thenProceed(with: WorkflowItem(MapFeatureView.self)) .tabItem { @@ -28,7 +28,7 @@ struct ContentView: View { } .tag(Tab.map) - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(QRScannerFeatureOnboardingView.self)) .thenProceed(with: WorkflowItem(QRScannerFeatureView.self)) .tabItem { @@ -36,7 +36,7 @@ struct ContentView: View { } .tag(Tab.qr) - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(ProfileFeatureOnboardingView.self)) .thenProceed(with: WorkflowItem(ProfileFeatureView.self)) .tabItem { diff --git a/ExampleApps/SwiftUIExample/Views/Profile/AccountInformationView.swift b/ExampleApps/SwiftUIExample/Views/Profile/AccountInformationView.swift index 51cfde305..0d6719653 100644 --- a/ExampleApps/SwiftUIExample/Views/Profile/AccountInformationView.swift +++ b/ExampleApps/SwiftUIExample/Views/Profile/AccountInformationView.swift @@ -30,7 +30,7 @@ struct AccountInformationView: View, FlowRepresentable { } } } else { - WorkflowView(isLaunched: $usernameWorkflowLaunched, startingArgs: username) + WorkflowLauncher(isLaunched: $usernameWorkflowLaunched, startingArgs: username) .thenProceed(with: WorkflowItem(MFAView.self)) .thenProceed(with: WorkflowItem(ChangeUsernameView.self)) .onFinish { @@ -44,7 +44,7 @@ struct AccountInformationView: View, FlowRepresentable { passwordWorkflowLaunched = true } } else { - WorkflowView(isLaunched: $passwordWorkflowLaunched, startingArgs: password) + WorkflowLauncher(isLaunched: $passwordWorkflowLaunched, startingArgs: password) .thenProceed(with: WorkflowItem(MFAView.self)) .thenProceed(with: WorkflowItem(ChangePasswordView.self)) .onFinish { diff --git a/README.md b/README.md index 942e31e8a..21fa65cf1 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Then from your ContentView body, add: ```swift import SwiftCurrent_SwiftUI ... -WorkflowView(isLaunched: .constant(true), startingArgs: "Launched") +WorkflowLauncher(isLaunched: .constant(true), startingArgs: "Launched") .thenProceed(with: WorkflowItem(ExampleView.self)) ``` diff --git a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift index f99915226..258714a4b 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift @@ -11,10 +11,10 @@ import SwiftUI import SwiftCurrent /** - A view used to store complex view type information from a `Workflow` + A view created by a `WorkflowLauncher`. ### Discussion - You do not instantiate this view directly, rather you call `thenProceed(with:)` on a `WorkflowView`. + You do not instantiate this view directly, rather you call `thenProceed(with:)` on a `WorkflowLauncher`. */ @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) public struct ModifiedWorkflowView: View { @@ -46,41 +46,41 @@ public struct ModifiedWorkflowView: View { } } - init(_ workflowView: WorkflowView, isLaunched: Binding, item: WorkflowItem) where Wrapped == Never, Args == FR.WorkflowOutput { + init(_ WorkflowLauncher: WorkflowLauncher, isLaunched: Binding, item: WorkflowItem) where Wrapped == Never, Args == FR.WorkflowOutput { wrapped = nil let wf = AnyWorkflow(Workflow(item.metadata)) workflow = wf - launchArgs = workflowView.passedArgs + launchArgs = WorkflowLauncher.passedArgs _isLaunched = isLaunched - onFinish = workflowView.onFinish - onAbandon = workflowView.onAbandon + onFinish = WorkflowLauncher.onFinish + onAbandon = WorkflowLauncher.onAbandon let model = WorkflowViewModel(isLaunched: isLaunched) _model = StateObject(wrappedValue: model) _launcher = StateObject(wrappedValue: Launcher(workflow: wf, responder: model, - launchArgs: workflowView.passedArgs)) + launchArgs: WorkflowLauncher.passedArgs)) } - private init(_ workflowView: ModifiedWorkflowView, item: WorkflowItem) where Wrapped == ModifiedWorkflowView, Args == FR.WorkflowOutput { - _model = workflowView._model - wrapped = workflowView - workflow = workflowView.workflow + private init(_ WorkflowLauncher: ModifiedWorkflowView, item: WorkflowItem) where Wrapped == ModifiedWorkflowView, Args == FR.WorkflowOutput { + _model = WorkflowLauncher._model + wrapped = WorkflowLauncher + workflow = WorkflowLauncher.workflow workflow.append(item.metadata) - launchArgs = workflowView.launchArgs - _isLaunched = workflowView._isLaunched - _launcher = workflowView._launcher - onAbandon = workflowView.onAbandon + launchArgs = WorkflowLauncher.launchArgs + _isLaunched = WorkflowLauncher._isLaunched + _launcher = WorkflowLauncher._launcher + onAbandon = WorkflowLauncher.onAbandon } - private init(workflowView: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) { - _model = workflowView._model - wrapped = workflowView.wrapped - workflow = workflowView.workflow + private init(WorkflowLauncher: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) { + _model = WorkflowLauncher._model + wrapped = WorkflowLauncher.wrapped + workflow = WorkflowLauncher.workflow self.onFinish = onFinish self.onAbandon = onAbandon - launchArgs = workflowView.launchArgs - _isLaunched = workflowView._isLaunched - _launcher = workflowView._launcher + launchArgs = WorkflowLauncher.launchArgs + _isLaunched = WorkflowLauncher._isLaunched + _launcher = WorkflowLauncher._launcher } private func launch() { @@ -97,14 +97,14 @@ public struct ModifiedWorkflowView: View { public func onFinish(closure: @escaping (AnyWorkflow.PassedArgs) -> Void) -> Self { var onFinish = self.onFinish onFinish.append(closure) - return Self(workflowView: self, onFinish: onFinish, onAbandon: onAbandon) + return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon) } /// Adds an action to perform when this `Workflow` has abandoned. public func onAbandon(closure: @escaping () -> Void) -> Self { var onAbandon = self.onAbandon onAbandon.append(closure) - return Self(workflowView: self, onFinish: onFinish, onAbandon: onAbandon) + return Self(WorkflowLauncher: self, onFinish: onFinish, onAbandon: onAbandon) } } @@ -125,7 +125,7 @@ extension ModifiedWorkflowView where Args == Never { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == Never { ModifiedWorkflowView(self, item: item) @@ -137,7 +137,7 @@ extension ModifiedWorkflowView where Args == AnyWorkflow.PassedArgs { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, item: item) @@ -146,7 +146,7 @@ extension ModifiedWorkflowView where Args == AnyWorkflow.PassedArgs { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView { ModifiedWorkflowView(self, item: item) @@ -158,7 +158,7 @@ extension ModifiedWorkflowView { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where Args == FR.WorkflowInput { ModifiedWorkflowView(self, item: item) @@ -167,7 +167,7 @@ extension ModifiedWorkflowView { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, item: item) diff --git a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowView.swift b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift similarity index 81% rename from Sources/SwiftCurrent_SwiftUI/Views/WorkflowView.swift rename to Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift index 110b9d6c8..1a344bd0c 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/WorkflowView.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/WorkflowLauncher.swift @@ -1,5 +1,5 @@ // -// WorkflowView.swift +// WorkflowLauncher.swift // SwiftCurrent // // Created by Tyler Thompson on 7/12/21. @@ -10,15 +10,15 @@ import SwiftUI import SwiftCurrent /** - A view used to build a `Workflow` in SwiftUI. + Used to build a `Workflow` in SwiftUI; call thenProceed to create a SwiftUI view. ### Discussion - The preferred method for creating a `Workflow` with SwiftUI is a combination of `WorkflowView` and `WorkflowItem`. Initialize with arguments if your first `FlowRepresentable` has an input type. + The preferred method for creating a `Workflow` with SwiftUI is a combination of `WorkflowLauncher` and `WorkflowItem`. Initialize with arguments if your first `FlowRepresentable` has an input type. #### Example */ /// ```swift -/// WorkflowView(isLaunched: $isLaunched.animation(), args: "String in") +/// WorkflowLauncher(isLaunched: $isLaunched.animation(), args: "String in") /// .thenProceed(with: WorkflowItem(FirstView.self) /// .applyModifiers { /// if true { // Enabling transition animation @@ -38,24 +38,20 @@ import SwiftCurrent /// .animation(.easeInOut) /// } /// }) -/// .onAbandon { print("presentingWorkflowView is now false") } +/// .onAbandon { print("isLaunched is now false") } /// .onFinish { args in print("Finished 1: \(args)") } /// .onFinish { print("Finished 2: \($0)") } /// .background(Color.green) /// ``` @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -public struct WorkflowView: View { +public struct WorkflowLauncher { @Binding private var isLaunched: Bool var passedArgs = AnyWorkflow.PassedArgs.none var onFinish = [(AnyWorkflow.PassedArgs) -> Void]() var onAbandon = [() -> Void]() - public var body: some View { - noView() - } - /** - Creates a `WorkflowView` that displays a `FlowRepresentable` when presented. + Creates a base for proceeding with a `WorkflowItem`. - Parameter isLaunched: binding that controls launching the underlying `Workflow`. */ public init(isLaunched: Binding) where Args == Never { @@ -63,7 +59,7 @@ public struct WorkflowView: View { } /** - Creates a `WorkflowView` that displays a `FlowRepresentable` when presented. + Creates a base for proceeding with a `WorkflowItem`. - Parameter isLaunched: binding that controls launching the underlying `Workflow`. - Parameter startingArgs: arguments passed to the first `FlowRepresentable` in the underlying `Workflow`. */ @@ -105,19 +101,14 @@ public struct WorkflowView: View { onFinish: onFinish, onAbandon: onAbandon) } - - // swiftlint:disable:next unavailable_function - private func noView() -> Text { - fatalError("WorkflowView needs information about what to present, make sure to call `.thenProceed(with:)`") - } } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension WorkflowView where Args == Never { +extension WorkflowLauncher where Args == Never { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == Never { ModifiedWorkflowView(self, isLaunched: _isLaunched, item: item) @@ -125,11 +116,11 @@ extension WorkflowView where Args == Never { } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension WorkflowView where Args == AnyWorkflow.PassedArgs { +extension WorkflowLauncher where Args == AnyWorkflow.PassedArgs { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, isLaunched: _isLaunched, item: item) @@ -138,7 +129,7 @@ extension WorkflowView where Args == AnyWorkflow.PassedArgs { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView { ModifiedWorkflowView(self, isLaunched: _isLaunched, item: item) @@ -146,11 +137,11 @@ extension WorkflowView where Args == AnyWorkflow.PassedArgs { } @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension WorkflowView { +extension WorkflowLauncher { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where Args == FR.WorkflowInput { ModifiedWorkflowView(self, isLaunched: _isLaunched, item: item) @@ -159,7 +150,7 @@ extension WorkflowView { /** 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 `WorkflowView` with the additional `FlowRepresentable` item. + - Returns: a new `ModifiedWorkflowView` with the additional `FlowRepresentable` item. */ public func thenProceed(with item: WorkflowItem) -> ModifiedWorkflowView where FR.WorkflowInput == AnyWorkflow.PassedArgs { ModifiedWorkflowView(self, isLaunched: _isLaunched, item: item) diff --git a/Sources/SwiftCurrent_SwiftUI/WorkflowItem.swift b/Sources/SwiftCurrent_SwiftUI/WorkflowItem.swift index 91577a827..52e42d77e 100644 --- a/Sources/SwiftCurrent_SwiftUI/WorkflowItem.swift +++ b/Sources/SwiftCurrent_SwiftUI/WorkflowItem.swift @@ -11,7 +11,7 @@ import SwiftUI import SwiftCurrent /** - A concrete type used to modify a `FlowRepresentable` in a `WorkflowView`. + A concrete type used to modify a `FlowRepresentable` in a workflow. ### Discussion `WorkflowItem` gives you the ability to specify changes you'd like to apply to a specific `FlowRepresentable` when it is time to present it in a `Workflow`. diff --git a/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift b/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift index 8b82ef219..3338d0dc2 100644 --- a/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift @@ -34,7 +34,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self).persistence(.removedAfterProceeding)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -68,7 +68,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.removedAfterProceeding)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -105,7 +105,7 @@ final class PersistenceTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -141,7 +141,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.removedAfterProceeding)) .thenProceed(with: WorkflowItem(FR3.self).persistence(.removedAfterProceeding)) @@ -180,7 +180,7 @@ final class PersistenceTests: XCTestCase { let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: binding) + WorkflowLauncher(isLaunched: binding) .thenProceed(with: WorkflowItem(FR1.self).persistence(.removedAfterProceeding)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.removedAfterProceeding)) .thenProceed(with: WorkflowItem(FR3.self).persistence(.removedAfterProceeding)) @@ -226,7 +226,7 @@ final class PersistenceTests: XCTestCase { let expectOnFinish = expectation(description: "OnFinish called") let expectedStart = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: binding, startingArgs: expectedStart) + WorkflowLauncher(isLaunched: binding, startingArgs: expectedStart) .thenProceed(with: WorkflowItem(FR1.self).persistence { XCTAssertEqual($0, expectedStart) return .removedAfterProceeding @@ -273,7 +273,7 @@ final class PersistenceTests: XCTestCase { let expectOnFinish = expectation(description: "OnFinish called") let expectedStart = AnyWorkflow.PassedArgs.args(UUID().uuidString) let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: binding, startingArgs: expectedStart) + WorkflowLauncher(isLaunched: binding, startingArgs: expectedStart) .thenProceed(with: WorkflowItem(FR1.self) .persistence { XCTAssertNotNil(expectedStart.extractArgs(defaultValue: 1) as? String) @@ -320,7 +320,7 @@ final class PersistenceTests: XCTestCase { let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: binding) + WorkflowLauncher(isLaunched: binding) .thenProceed(with: WorkflowItem(FR1.self) .persistence { .removedAfterProceeding }) .thenProceed(with: WorkflowItem(FR2.self).persistence(.removedAfterProceeding)) @@ -363,7 +363,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -398,7 +398,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -434,7 +434,7 @@ final class PersistenceTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -469,7 +469,7 @@ final class PersistenceTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) .thenProceed(with: WorkflowItem(FR3.self).persistence(.persistWhenSkipped)) @@ -508,7 +508,7 @@ final class PersistenceTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self).persistence(.persistWhenSkipped)) .thenProceed(with: WorkflowItem(FR2.self).persistence(.persistWhenSkipped)) .thenProceed(with: WorkflowItem(FR3.self).persistence(.persistWhenSkipped)) diff --git a/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift b/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift index 36b2c1922..d2ecca518 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift @@ -35,7 +35,7 @@ final class SkipTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -69,7 +69,7 @@ final class SkipTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -104,7 +104,7 @@ final class SkipTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -140,7 +140,7 @@ final class SkipTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -178,7 +178,7 @@ final class SkipTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index bd86e7649..9e49ef371 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -26,7 +26,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .onFinish { _ in @@ -41,16 +41,6 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnFinish, expectViewLoaded], timeout: 0.5) } - func testWorkflowViewThrowsFatalErrorIfThenProceedNeverGetsCalled() { - XCTAssertThrowsFatalError { - _ = WorkflowView(isLaunched: .constant(true)).body - } - - XCTAssertThrowsFatalError { - _ = WorkflowView(isLaunched: .constant(true), startingArgs: "").body - } - } - func testWorkflowCanHaveMultipleOnFinishClosures() throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? @@ -63,7 +53,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectOnFinish1 = expectation(description: "OnFinish1 called") let expectOnFinish2 = expectation(description: "OnFinish2 called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .onFinish { _ in expectOnFinish1.fulfill() @@ -87,7 +77,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .onFinish { _ in expectOnFinish.fulfill() } // what kind of monster does this?! .thenProceed(with: WorkflowItem(FR2.self))).inspection.inspect { viewUnderTest in @@ -111,7 +101,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expectOnFinish = expectation(description: "OnFinish called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .onFinish { _ in expectOnFinish.fulfill() } // what kind of monster does this?! .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self))).inspection.inspect { viewUnderTest in @@ -135,7 +125,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expected = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: expected) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expected) .thenProceed(with: WorkflowItem(FR1.self))).inspection.inspect { viewUnderTest in XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().stringProperty, expected) } @@ -154,7 +144,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expected = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: expected) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expected) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR1.self))).inspection.inspect { viewUnderTest in XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) @@ -175,7 +165,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expected = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expected)) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expected)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR1.self))).inspection.inspect { viewUnderTest in XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) @@ -217,7 +207,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectedFR3 = Bool.random() let expectedEnd = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: expectedFR1) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedFR1) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -265,7 +255,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { var body: some View { Text("FR7 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -300,7 +290,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { var body: some View { Text("FR3 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -334,7 +324,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { var body: some View { Text("FR4 type") } } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) @@ -361,7 +351,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let isLaunched = Binding(wrappedValue: true) let expectOnAbandon = expectation(description: "OnAbandon called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: isLaunched) + WorkflowLauncher(isLaunched: isLaunched) .thenProceed(with: WorkflowItem(FR1.self)) .onAbandon { XCTAssertFalse(isLaunched.wrappedValue) @@ -375,7 +365,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnAbandon, expectViewLoaded], timeout: 0.5) } - func testWorkflowViewCanHaveMultipleOnAbandonCallbacks() throws { + func testWorkflowCanHaveMultipleOnAbandonCallbacks() throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -384,7 +374,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectOnAbandon1 = expectation(description: "OnAbandon1 called") let expectOnAbandon2 = expectation(description: "OnAbandon2 called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: isLaunched) + WorkflowLauncher(isLaunched: isLaunched) .thenProceed(with: WorkflowItem(FR1.self)) .onAbandon { XCTAssertFalse(isLaunched.wrappedValue) @@ -400,7 +390,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnAbandon1, expectOnAbandon2, expectViewLoaded], timeout: 0.5) } - func testWorkflowViewCanAbandonEvenIfItIsNotTheLastStatement() throws { + func testWorkflowCanAbandonEvenIfItIsNotTheLastStatement() throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -408,7 +398,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let isLaunched = Binding(wrappedValue: true) let expectOnAbandon = expectation(description: "OnAbandon called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: isLaunched) + WorkflowLauncher(isLaunched: isLaunched) .thenProceed(with: WorkflowItem(FR1.self)) .onAbandon { // Again, MONSTERS do this XCTAssertFalse(isLaunched.wrappedValue) @@ -422,7 +412,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnAbandon, expectViewLoaded], timeout: 0.5) } - func testWorkflowViewCanAbandonEvenIfItIsTheFirstStatement() throws { + func testWorkflowLauncherCanAbandonEvenIfItIsTheFirstStatement() throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -430,7 +420,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let isLaunched = Binding(wrappedValue: true) let expectOnAbandon = expectation(description: "OnAbandon called") let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: isLaunched) + WorkflowLauncher(isLaunched: isLaunched) .onAbandon { // Again, MONSTERS do this XCTAssertFalse(isLaunched.wrappedValue) expectOnAbandon.fulfill() @@ -444,7 +434,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnAbandon, expectViewLoaded], timeout: 0.5) } - func testWorkflowViewCanHaveModifiers() throws { + func testWorkflowCanHaveModifiers() throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -453,7 +443,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self) .applyModifiers { $0.customModifier().background(Color.blue) })).inspection.inspect { viewUnderTest in XCTAssertNoThrow(try viewUnderTest.find(FR1.self).background()) @@ -475,7 +465,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } let binding = Binding(wrappedValue: true) - let workflowView = WorkflowView(isLaunched: binding) + let workflowView = WorkflowLauncher(isLaunched: binding) .thenProceed(with: WorkflowItem(FR1.self) .applyModifiers { $0.customModifier().background(Color.blue) }) .thenProceed(with: WorkflowItem(FR2.self)) @@ -514,7 +504,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: expectedArgs) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .onFinish { _ in @@ -548,7 +538,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expectedArgs)) + WorkflowLauncher(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expectedArgs)) .thenProceed(with: WorkflowItem(FR1.self)) .onFinish { _ in expectOnFinish.fulfill() @@ -587,7 +577,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString let expectViewLoaded = ViewHosting.loadView( - WorkflowView(isLaunched: .constant(true)) + WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) .thenProceed(with: WorkflowItem(FR2.self)) .thenProceed(with: WorkflowItem(FR3.self)) diff --git a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift index 54d670d9a..6b52f50a6 100644 --- a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift +++ b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/InspectableExtensions.swift @@ -12,9 +12,6 @@ import SwiftUI @testable import SwiftCurrent_SwiftUI // Don't forget you need to make every view you want to test with ViewInspector Inspectable -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension WorkflowView: Inspectable { } - @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension ModifiedWorkflowView: Inspectable { } diff --git a/wiki/Getting-Started-SwiftUI.md b/wiki/Getting-Started-SwiftUI.md index 8b4106995..58a7a3b9e 100644 --- a/wiki/Getting-Started-SwiftUI.md +++ b/wiki/Getting-Started-SwiftUI.md @@ -2,7 +2,7 @@ This guide will walk you through getting a [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) up and running in a new iOS project. If you would like to see an existing project, clone the repo and view the `SwiftUIExample` scheme in `SwiftCurrent.xcworkspace`. -The app in this guide is going to be very simple. It consists of a view that will host the [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html), a view to enter an email address, and an optional view for when the user enters an email with `@wwt.com` in it. Here is a preview of what the app will look like: +The app in this guide is going to be very simple. It consists of a view that will host the [WorkflowLauncher](https://wwt.github.io/SwiftCurrent/Structs/WorkflowLauncher.html), a view to enter an email address, and an optional view for when the user enters an email with `@wwt.com` in it. Here is a preview of what the app will look like: ![Preview image of app](https://github.com/wwt/SwiftCurrent/blob/36c45fcc3cc66dba16d5d5c78bcd4bc865175a34/wiki/swiftUI.gif) @@ -83,7 +83,7 @@ struct SecondView_Previews: PreviewProvider {
-The [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) protocol requires there to be a `_workflowPointer` on your object, but protocols cannot enforce you to use `weak`. If you do not put `weak var _workflowPointer`, the [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) will end up with a strong circular reference when placed in a [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html). +The [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) protocol requires there to be a `_workflowPointer` on your object, but protocols cannot enforce you to use `weak`. If you do not put `weak var _workflowPointer`, the [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html) will end up with a strong circular reference when placed in a [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html).
#### **What's this `shouldLoad()`?** @@ -102,7 +102,7 @@ It is part of the [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protoco ## Launching the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) -Next we add a [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html) to the body of our starting app view, in this case `ContentView`. +Next we add a [WorkflowLauncher](https://wwt.github.io/SwiftCurrent/Structs/WorkflowLauncher.html) to the body of our starting app view, in this case `ContentView`. ```swift import SwiftUI @@ -114,7 +114,7 @@ struct ContentView: View { if !workflowIsPresented { Button("Present") { $workflowIsPresented.wrappedValue = true } } - WorkflowView(isLaunched: $workflowIsPresented, startingArgs: "SwiftCurrent") // SwiftCurrent + WorkflowLauncher(isLaunched: $workflowIsPresented, startingArgs: "SwiftCurrent") // SwiftCurrent .thenProceed(with: WorkflowItem(FirstView.self) // SwiftCurrent .applyModifiers { firstView in firstView.padding().border(.gray) }) .thenProceed(with: WorkflowItem(SecondView.self) // SwiftCurrent @@ -143,14 +143,14 @@ struct Content_Previews: PreviewProvider {
-In SwiftUI, the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) type is handled by the [WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html). That view contains an underlying [AnyWorkflow](https://wwt.github.io/SwiftCurrent/Classes/AnyWorkflow.html) that it manages and exposes as appropriate. +In SwiftUI, the [Workflow](https://wwt.github.io/SwiftCurrent/Classes/Workflow.html) type is handled by the library when you start with a [WorkflowLauncher](https://wwt.github.io/SwiftCurrent/Structs/WorkflowLauncher.html).
#### **Where is the type safety, I heard about?**
-[WorkflowView](https://wwt.github.io/SwiftCurrent/Structs/WorkflowView.html) is specialized with your `startingArgs` type. In [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html), these types are supplied by the `WorkflowInput` and `WorkflowOutput` associated types. These all work together to create compile-time type safety when creating your flow. This means that you will get a build error if the output of `FirstView` does not match the input type of `SecondView`. +[WorkflowLauncher](https://wwt.github.io/SwiftCurrent/Structs/WorkflowLauncher.html) is specialized with your `startingArgs` type. In [FlowRepresentable](https://wwt.github.io/SwiftCurrent/Protocols/FlowRepresentable.html), these types are supplied by the `WorkflowInput` and `WorkflowOutput` associated types. These all work together to create compile-time type safety when creating your flow. This means that you will get a build error if the output of `FirstView` does not match the input type of `SecondView`.
#### **What's going on with this `startingArgs` and `passedArgs`?**