diff --git a/.github/workflows/PR_CI.yml b/.github/workflows/PR_CI.yml index 39bbb777a..9b2e83bb8 100644 --- a/.github/workflows/PR_CI.yml +++ b/.github/workflows/PR_CI.yml @@ -56,4 +56,4 @@ jobs: - uses: actions/checkout@v2 - name: Swiftlint run: bundle exec fastlane lint - working-directory: ${{ env.working-directory }} \ No newline at end of file + working-directory: ${{ env.working-directory }} diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/UIKitInteropTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/UIKitInteropTests.swift index e7e1c286c..c4f272550 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/UIKitInteropTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/UIKitInteropTests.swift @@ -21,17 +21,21 @@ import SwiftCurrent_UIKit @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class UIKitInteropTests: XCTestCase, View { - func testPuttingAUIKitViewInsideASwiftUIWorkflow() throws { + func testPuttingAUIKitViewInsideASwiftUIWorkflow() async throws { let launchArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { - thenProceed(with: UIKitInteropProgrammaticViewController.self) + + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { + thenProceed(with: UIKitInteropProgrammaticViewController.self) + } } - var vc: UIKitInteropProgrammaticViewController! + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.view(ViewControllerWrapper<UIKitInteropProgrammaticViewController>.self) + try await MainActor.run { + let wrapper = try launcher.view(ViewControllerWrapper<UIKitInteropProgrammaticViewController>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<UIKitInteropProgrammaticViewController>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) + var vc = try wrapper.actualView().makeUIViewController(context: context) vc.removeFromParent() vc.loadOnDevice() @@ -44,62 +48,55 @@ final class UIKitInteropTests: XCTestCase, View { } XCTAssertEqual(vc.saveButton?.willRespondToUser, true) - XCTAssertEqual(vc?.emailTextField?.willRespondToUser, true) + XCTAssertEqual(vc.emailTextField?.willRespondToUser, true) vc.emailTextField?.simulateTouch() - vc.emailTextField?.simulateTyping(vc?.welcomeLabel?.text) + vc.emailTextField?.simulateTyping(vc.welcomeLabel?.text) vc.saveButton?.simulateTouch() self.wait(for: [proceedCalled], timeout: TestConstant.timeout) } - - wait(for: [exp], timeout: TestConstant.timeout) } - func testPuttingAUIKitViewInsideASwiftUIWorkflowWithOtherSwiftUIViews() throws { + func testPuttingAUIKitViewInsideASwiftUIWorkflowWithOtherSwiftUIViews() async throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? - var body: some View { EmptyView() } + let str: String + init(with str: String) { + self.str = str + } + var body: some View { Text(str) } } let launchArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { - thenProceed(with: UIKitInteropProgrammaticViewController.self) { - thenProceed(with: FR1.self) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { + thenProceed(with: UIKitInteropProgrammaticViewController.self) { + thenProceed(with: FR1.self) + } } } - var vc: UIKitInteropProgrammaticViewController! + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.view(ViewControllerWrapper<UIKitInteropProgrammaticViewController>.self) + try await MainActor.run { + let wrapper = try launcher.view(ViewControllerWrapper<UIKitInteropProgrammaticViewController>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<UIKitInteropProgrammaticViewController>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) + let vc = try wrapper.actualView().makeUIViewController(context: context) vc.removeFromParent() vc.loadOnDevice() XCTAssertUIViewControllerDisplayed(isInstance: vc) - let proceedCalled = self.expectation(description: "proceedCalled") - vc.proceedInWorkflowStorage = { args in - XCTAssertEqual(args.extractArgs(defaultValue: nil) as? String, "Welcome \(launchArgs)!") - proceedCalled.fulfill() - } - XCTAssertEqual(vc.saveButton?.willRespondToUser, true) - XCTAssertEqual(vc?.emailTextField?.willRespondToUser, true) + XCTAssertEqual(vc.emailTextField?.willRespondToUser, true) vc.emailTextField?.simulateTouch() - vc.emailTextField?.simulateTyping(vc?.welcomeLabel?.text) + vc.emailTextField?.simulateTyping(vc.welcomeLabel?.text) vc.saveButton?.simulateTouch() - self.wait(for: [proceedCalled], timeout: TestConstant.timeout) - - try workflowLauncher.actualView().inspectWrapped { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self)) - } + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "Welcome \(launchArgs)!") } - - wait(for: [exp], timeout: TestConstant.timeout) } - func testPuttingAUIKitViewThatDoesNotTakeInDataInsideASwiftUIWorkflow() throws { + func testPuttingAUIKitViewThatDoesNotTakeInDataInsideASwiftUIWorkflow() async throws { final class FR1: UIWorkflowItem<Never, Never>, FlowRepresentable { let nextButton = UIButton() @@ -119,37 +116,39 @@ final class UIKitInteropTests: XCTestCase, View { nextButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) + + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) + } } - var vc: FR1! + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.view(ViewControllerWrapper<FR1>.self) + try await MainActor.run { + let wrapper = try workflowView.view(ViewControllerWrapper<FR1>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<FR1>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) - } + var vc = try wrapper.actualView().makeUIViewController(context: context) - wait(for: [exp], timeout: TestConstant.timeout) - - vc.removeFromParent() - vc.loadOnDevice() + vc.removeFromParent() + vc.loadOnDevice() - XCTAssertUIViewControllerDisplayed(isInstance: vc) + XCTAssertUIViewControllerDisplayed(isInstance: vc) - let proceedCalled = expectation(description: "proceedCalled") - vc.proceedInWorkflowStorage = { _ in - proceedCalled.fulfill() - } + let proceedCalled = expectation(description: "proceedCalled") + vc.proceedInWorkflowStorage = { _ in + proceedCalled.fulfill() + } - XCTAssertEqual(vc.nextButton.willRespondToUser, true) - vc.nextButton.simulateTouch() + XCTAssertEqual(vc.nextButton.willRespondToUser, true) + vc.nextButton.simulateTouch() - wait(for: [proceedCalled], timeout: TestConstant.timeout) + wait(for: [proceedCalled], timeout: TestConstant.timeout) + } } - func testWorkflowPointerIsSetBeforeShouldLoadIsCalled() throws { + func testWorkflowPointerIsSetBeforeShouldLoadIsCalled() async throws { final class FR1: UIWorkflowItem<Never, String>, FlowRepresentable { func shouldLoad() -> Bool { proceedInWorkflow("FR1") @@ -163,88 +162,89 @@ final class UIKitInteropTests: XCTestCase, View { } required init?(coder: NSCoder) { nil } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - var vc: FR2! + .hostAndInspect(with: \.inspection) - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.find(ViewControllerWrapper<FR2>.self) + try await MainActor.run { + let wrapper = try launcher.find(ViewControllerWrapper<FR2>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<FR2>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) - } - - wait(for: [exp], timeout: TestConstant.timeout) - - vc.removeFromParent() - vc.loadOnDevice() + let vc = try wrapper.actualView().makeUIViewController(context: context) + vc.removeFromParent() + vc.loadOnDevice() - XCTAssertUIViewControllerDisplayed(isInstance: vc) + XCTAssertUIViewControllerDisplayed(isInstance: vc) + } } - func testPuttingAUIKitViewFromStoryboardInsideASwiftUIWorkflow() throws { + func testPuttingAUIKitViewFromStoryboardInsideASwiftUIWorkflow() async throws { let launchArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { - thenProceed(with: TestInputViewController.self) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: launchArgs) { + thenProceed(with: TestInputViewController.self) + } } - var vc: TestInputViewController! + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.view(ViewControllerWrapper<TestInputViewController>.self) + try await MainActor.run { + let wrapper = try launcher.view(ViewControllerWrapper<TestInputViewController>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<TestInputViewController>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) - } + var vc = try wrapper.actualView().makeUIViewController(context: context) - wait(for: [exp], timeout: TestConstant.timeout) - - vc.removeFromParent() - vc.loadOnDevice() + vc.removeFromParent() + vc.loadOnDevice() - XCTAssertUIViewControllerDisplayed(isInstance: vc) + XCTAssertUIViewControllerDisplayed(isInstance: vc) - let proceedCalled = expectation(description: "proceedCalled") - vc.proceedInWorkflowStorage = { _ in - proceedCalled.fulfill() - } + let proceedCalled = expectation(description: "proceedCalled") + vc.proceedInWorkflowStorage = { _ in + proceedCalled.fulfill() + } - vc.proceedInWorkflow() + vc.proceedInWorkflow() - wait(for: [proceedCalled], timeout: TestConstant.timeout) + wait(for: [proceedCalled], timeout: TestConstant.timeout) + } } - func testPuttingAUIKitViewFromStoryboardThatDoesNotTakeInDataInsideASwiftUIWorkflow() throws { - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: TestNoInputViewController.self) + func testPuttingAUIKitViewFromStoryboardThatDoesNotTakeInDataInsideASwiftUIWorkflow() async throws { + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: TestNoInputViewController.self) + } } - var vc: TestNoInputViewController! + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - let wrapper = try workflowLauncher.view(ViewControllerWrapper<TestNoInputViewController>.self) + try await MainActor.run { + let wrapper = try launcher.view(ViewControllerWrapper<TestNoInputViewController>.self) let context = unsafeBitCast(FakeContext(), to: UIViewControllerRepresentableContext<ViewControllerWrapper<TestNoInputViewController>>.self) - vc = try wrapper.actualView().makeUIViewController(context: context) - } - - wait(for: [exp], timeout: TestConstant.timeout) + var vc = try wrapper.actualView().makeUIViewController(context: context) - vc.removeFromParent() - vc.loadOnDevice() + vc.removeFromParent() + vc.loadOnDevice() - XCTAssertUIViewControllerDisplayed(isInstance: vc) + XCTAssertUIViewControllerDisplayed(isInstance: vc) - let proceedCalled = expectation(description: "proceedCalled") - vc.proceedInWorkflowStorage = { _ in - proceedCalled.fulfill() - } + let proceedCalled = expectation(description: "proceedCalled") + vc.proceedInWorkflowStorage = { _ in + proceedCalled.fulfill() + } - vc.proceedInWorkflow() + vc.proceedInWorkflow() - wait(for: [proceedCalled], timeout: TestConstant.timeout) + wait(for: [proceedCalled], timeout: TestConstant.timeout) + } } - func testPuttingAUIKitViewThatDoesNotLoadInsideASwiftUIWorkflow() throws { + func testPuttingAUIKitViewThatDoesNotLoadInsideASwiftUIWorkflow() async throws { final class FR1: UIWorkflowItem<Never, Never>, FlowRepresentable { func shouldLoad() -> Bool { false } } @@ -256,25 +256,26 @@ final class UIKitInteropTests: XCTestCase, View { Text("FR2") } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let exp = ViewHosting.loadView(workflowView).inspection.inspect { workflowLauncher in - XCTAssertThrowsError(try workflowLauncher.view(ViewControllerWrapper<FR1>.self)) - XCTAssertEqual(try workflowLauncher.find(FR2.self).text().string(), "FR2") + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [exp], timeout: TestConstant.timeout) + XCTAssertThrowsError(try launcher.view(ViewControllerWrapper<FR1>.self)) + XCTAssertEqual(try launcher.find(FR2.self).text().string(), "FR2") } } extension UIViewController { func loadOnDevice() { // UIUTest's loadForTesting method does not work because it uses the deprecated `keyWindow` property. - let window = UIApplication.shared.windows.first + let window = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first?.windows.first window?.removeViewsFromRootViewController() window?.rootViewController = self diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/AccountInformationViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/AccountInformationViewTests.swift index 3466ef6ad..cf4da271b 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/AccountInformationViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/AccountInformationViewTests.swift @@ -34,31 +34,25 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver { private typealias MFAViewWorkflowView = WorkflowLauncher<WorkflowItem<MFAView, Never, MFAView>> - func testUpdatedAccountInformationView() throws { - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Email: ") - XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 1).string(), "SwiftCurrent@wwt.com") + func testUpdatedAccountInformationView() async throws { + let view = try await AccountInformationView().hostAndInspect(with: \.inspection) - XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 2).string(), "Password: ") - XCTAssertEqual(try view.find(ViewType.SecureField.self).input(), "supersecure") + XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Email: ") + XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 1).string(), "SwiftCurrent@wwt.com") - XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2) - } - wait(for: [exp], timeout: TestConstant.timeout) + XCTAssertEqual(try view.find(ViewType.Text.self, skipFound: 2).string(), "Password: ") + XCTAssertEqual(try view.find(ViewType.SecureField.self).input(), "supersecure") + + XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2) } - func testAccountInformationCanLaunchUsernameWorkflowAgnostic() throws { + func testAccountInformationCanLaunchUsernameWorkflowAgnostic() async throws { Self.workflowLaunchedData.removeAll() - var accountInformation: InspectableView<ViewType.View<AccountInformationView>>! - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - accountInformation = view - XCTAssertFalse(try view.actualView().emailWorkflowLaunched) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssert(try view.actualView().emailWorkflowLaunched) - } - wait(for: [exp], timeout: TestConstant.timeout) - XCTAssertNotNil(accountInformation) + let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection) + XCTAssertFalse(try accountInformation.actualView().emailWorkflowLaunched) + XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self).tap()) + XCTAssert(try accountInformation.actualView().emailWorkflowLaunched) waitUntil(Self.workflowTestingData != nil) let data = Self.workflowTestingData @@ -79,49 +73,38 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver { // Complete workflow (Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args("new email")) - wait(for: [ - ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in - XCTAssertEqual(try view.actualView().email, "new email") - XCTAssertFalse(try view.actualView().emailWorkflowLaunched) - } - ], timeout: TestConstant.timeout) + let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection) + XCTAssertEqual(try view.actualView().email, "new email") + XCTAssertFalse(try view.actualView().emailWorkflowLaunched) } - func testAccountInformationDoesNotBlowUp_IfUsernameWorkflowReturnsSomethingWEIRD() throws { + func testAccountInformationDoesNotBlowUp_IfUsernameWorkflowReturnsSomethingWEIRD() async throws { class CustomObj { } Self.workflowLaunchedData.removeAll() - var accountInformation: InspectableView<ViewType.View<AccountInformationView>>! - var expectedEmail = "starting value" - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - accountInformation = view - expectedEmail = try view.actualView().email - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - } - wait(for: [exp], timeout: TestConstant.timeout) - if Self.workflowTestingData == nil { throw XCTSkip("test data was not created") } + let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection) + let expectedEmail = try accountInformation.actualView().email + + XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self).tap()) + + waitUntil(Self.workflowTestingData != nil) + + XCTAssertNotNil(Self.workflowTestingData) (Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args(CustomObj())) - wait(for: [ - ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in - XCTAssert(try view.actualView().emailWorkflowLaunched) - XCTAssertEqual(try view.actualView().email, expectedEmail) - } - ].compactMap { $0 }, timeout: TestConstant.timeout) + let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection) + XCTAssert(try view.actualView().emailWorkflowLaunched) + XCTAssertEqual(try view.actualView().email, expectedEmail) } - func testAccountInformationCanLaunchPasswordWorkflowAgnostic() throws { + func testAccountInformationCanLaunchPasswordWorkflowAgnostic() async throws { Self.workflowLaunchedData.removeAll() - var accountInformation: InspectableView<ViewType.View<AccountInformationView>>! - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - accountInformation = view - XCTAssertFalse(try view.actualView().passwordWorkflowLaunched) - XCTAssertNoThrow(try view.find(ViewType.Button.self, skipFound: 1).tap()) - XCTAssert(try view.actualView().passwordWorkflowLaunched) - } - wait(for: [exp], timeout: TestConstant.timeout) - XCTAssertNotNil(accountInformation) + let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection) + + XCTAssertFalse(try accountInformation.actualView().passwordWorkflowLaunched) + XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self, skipFound: 1).tap()) + XCTAssert(try accountInformation.actualView().passwordWorkflowLaunched) waitUntil(Self.workflowTestingData != nil) let data = Self.workflowTestingData @@ -142,48 +125,39 @@ final class AccountInformationViewTests: XCTestCase, WorkflowTestingReceiver { // Complete workflow (Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args("newPassword")) - wait(for: [ - ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in - XCTAssertEqual(try view.actualView().password, "newPassword") - XCTAssertFalse(try view.actualView().passwordWorkflowLaunched) - } - ], timeout: TestConstant.timeout) + let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection) + XCTAssertEqual(try view.actualView().password, "newPassword") + XCTAssertFalse(try view.actualView().passwordWorkflowLaunched) } - func testAccountInformationDoesNotBlowUp_IfPasswordWorkflowReturnsSomethingWEIRD() throws { + func testAccountInformationDoesNotBlowUp_IfPasswordWorkflowReturnsSomethingWEIRD() async throws { class CustomObj { } Self.workflowLaunchedData.removeAll() - var accountInformation: InspectableView<ViewType.View<AccountInformationView>>! - var expectedPassword = "starting value" - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - accountInformation = view - expectedPassword = try view.actualView().password - XCTAssertNoThrow(try view.find(ViewType.Button.self, skipFound: 1).tap()) - } - wait(for: [exp], timeout: TestConstant.timeout) - if Self.workflowTestingData == nil { throw XCTSkip("test data was not created") } + let accountInformation = try await AccountInformationView().hostAndInspect(with: \.inspection) + let expectedPassword = try accountInformation.actualView().password + XCTAssertNoThrow(try accountInformation.find(ViewType.Button.self, skipFound: 1).tap()) + + waitUntil(Self.workflowTestingData != nil) + XCTAssertNotNil(Self.workflowTestingData) + (Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(.args(CustomObj())) - wait(for: [ - ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in - XCTAssert(try view.actualView().passwordWorkflowLaunched) - XCTAssertEqual(try view.actualView().password, expectedPassword) - } - ].compactMap { $0 }, timeout: TestConstant.timeout) + let view = try await accountInformation.actualView().hostAndInspect(with: \.inspection) + XCTAssert(try view.actualView().passwordWorkflowLaunched) + XCTAssertEqual(try view.actualView().password, expectedPassword) } - func testAccountInformationCanLaunchBothWorkflows() throws { - let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in - XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 0) + func testAccountInformationCanLaunchBothWorkflows() async throws { + let view = try await AccountInformationView().hostAndInspect(with: \.inspection) - let firstButton = try view.find(ViewType.Button.self) - let secondButton = try view.find(ViewType.Button.self, skipFound: 1) - XCTAssertNoThrow(try secondButton.tap()) - XCTAssertNoThrow(try firstButton.tap()) + XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 0) - XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 2) - } - wait(for: [exp], timeout: TestConstant.timeout) + let firstButton = try view.find(ViewType.Button.self) + let secondButton = try view.find(ViewType.Button.self, skipFound: 1) + XCTAssertNoThrow(try secondButton.tap()) + XCTAssertNoThrow(try firstButton.tap()) + + XCTAssertEqual(view.findAll(MFAViewWorkflowView.self).count, 2) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift index edd5ee5b7..536880901 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangePasswordViewTests.swift @@ -14,80 +14,97 @@ import SwiftUI @testable import SwiftUIExample final class ChangePasswordViewTests: XCTestCase, View { - func testChangePasswordView() throws { + func testChangePasswordView() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertEqual(view.findAll(PasswordField.self).count, 3) - XCTAssertNoThrow(try view.find(ViewType.Button.self)) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertEqual(view.findAll(PasswordField.self).count, 3) + XCTAssertNoThrow(try view.find(ViewType.Button.self)) } - func testChangePasswordProceeds_IfAllInformationIsCorrect() throws { + func testChangePasswordProceeds_IfAllInformationIsCorrect() async throws { let currentPassword = UUID().uuidString let onFinish = expectation(description: "onFinish called") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword) { - thenProceed(with: ChangePasswordView.self) - } - .onFinish { _ in onFinish.fulfill() }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1")) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + let view = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword) { + thenProceed(with: ChangePasswordView.self) + } + .onFinish { _ in onFinish.fulfill() } } - wait(for: [exp, onFinish], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1")) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + + wait(for: [onFinish], timeout: TestConstant.timeout) } - func testErrorsDoNotShowUp_IfFormWasNotSubmitted() throws { + func testErrorsDoNotShowUp_IfFormWasNotSubmitted() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1")) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1")) - XCTAssertNoThrow(try view.find(ViewType.Button.self)) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF1")) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput("asdfF1")) + XCTAssertNoThrow(try view.find(ViewType.Button.self)) } - func testIncorrectOldPassword_PrintsError() throws { + func testIncorrectOldPassword_PrintsError() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput("WRONG")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssert(try view.vStack().text(0).string().contains("Old password does not match records")) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput("WRONG")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssert(try view.vStack().text(0).string().contains("Old password does not match records")) } - func testPasswordsNotMatching_PrintsError() throws { + func testPasswordsNotMatching_PrintsError() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput(UUID().uuidString)) - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput(UUID().uuidString)) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssert(try view.vStack().text(0).string().contains("New password and confirmation password do not match")) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self).setInput(currentPassword)) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput(UUID().uuidString)) + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 2).setInput(UUID().uuidString)) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssert(try view.vStack().text(0).string().contains("New password and confirmation password do not match")) } - func testPasswordsNotHavingUppercase_PrintsError() throws { + func testPasswordsNotHavingUppercase_PrintsError() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdf1")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one uppercase character")) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdf1")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one uppercase character")) } - func testPasswordsNotHavingNumber_PrintsError() throws { + func testPasswordsNotHavingNumber_PrintsError() async throws { let currentPassword = UUID().uuidString - let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one number")) + let view = try await MainActor.run { + ChangePasswordView(with: currentPassword) } - wait(for: [exp], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.SecureField.self, skipFound: 1).setInput("asdfF")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one number")) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangeUsernameViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangeUsernameViewTests.swift index 1d9bd543d..e938d4ddc 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangeUsernameViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ChangeUsernameViewTests.swift @@ -13,17 +13,16 @@ import ViewInspector @testable import SwiftUIExample final class ChangeUsernameViewTests: XCTestCase { - func testChangeUsernameView() throws { + func testChangeUsernameView() async throws { let currentUsername = UUID().uuidString - let exp = ViewHosting.loadView(ChangeEmailView(with: currentUsername)).inspection.inspect { view in - XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), "New email: ") - XCTAssertEqual(try view.find(ViewType.TextField.self).labelView().text().string(), "\(currentUsername)") - XCTAssertNoThrow(try view.find(ViewType.Button.self)) - } - wait(for: [exp], timeout: TestConstant.timeout) + let view = try await ChangeEmailView(with: currentUsername).hostAndInspect(with: \.inspection) + + XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), "New email: ") + XCTAssertEqual(try view.find(ViewType.TextField.self).labelView().text().string(), "\(currentUsername)") + XCTAssertNoThrow(try view.find(ViewType.Button.self)) } - func testChangeUsernameViewProceedsWithCorrectDataWhenNameChanged() { + func testChangeUsernameViewProceedsWithCorrectDataWhenNameChanged() async throws { let newUsername = UUID().uuidString let proceedCalled = expectation(description: "Proceed called") let erased = AnyFlowRepresentableView(type: ChangeEmailView.self, args: .args("")) @@ -34,11 +33,12 @@ final class ChangeUsernameViewTests: XCTestCase { proceedCalled.fulfill() } changeUsernameView._workflowPointer = erased - let exp = ViewHosting.loadView(changeUsernameView).inspection.inspect { view in - XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), "New email: ") - XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput(newUsername)) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - } - wait(for: [exp, proceedCalled], timeout: TestConstant.timeout) + let view = try await changeUsernameView.hostAndInspect(with: \.inspection) + + XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), "New email: ") + XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput(newUsername)) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + + wait(for: [proceedCalled], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift index ee4bcc5db..048eb2bc7 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ContentViewTests.swift @@ -22,43 +22,28 @@ final class ContentViewTests: XCTestCase { Container.default.removeAll() } - func testContentView() throws { + func testContentView() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) Container.default.register(UserDefaults.self) { _ in defaults } - var wf1: MapWorkflow! - var wf2: QRScannerWorkflow! - var wf3: ProfileWorkflow! - let exp = ViewHosting.loadView(ContentView()).inspection.inspect { view in - wf1 = try view.tabView().view(MapWorkflow.self, 0).actualView() - XCTAssertEqual(try view.tabView().view(MapWorkflow.self, 0).tabItem().label().title().text().string(), "Map") - wf2 = try view.tabView().view(QRScannerWorkflow.self, 1).actualView() - XCTAssertEqual(try view.tabView().view(QRScannerWorkflow.self, 1).tabItem().label().title().text().string(), "QR Scanner") - wf3 = try view.tabView().view(ProfileWorkflow.self, 2).actualView() - XCTAssertEqual(try view.tabView().view(ProfileWorkflow.self, 2).tabItem().label().title().text().string(), "Profile") - } - wait(for: [exp], timeout: TestConstant.timeout) - XCTAssertNotNil(wf1) - XCTAssertNotNil(wf2) - XCTAssertNotNil(wf3) - wait(for: [ - ViewHosting.loadView(wf1).inspection.inspect { view in - XCTAssertNoThrow(try view.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(MapFeatureView.self)) - } - }, - ViewHosting.loadView(wf2).inspection.inspect { view in - XCTAssertNoThrow(try view.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(QRScannerFeatureView.self)) - } - }, - ViewHosting.loadView(wf3).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ProfileFeatureView.self)) - } - } - ].compactMap { $0 }, timeout: TestConstant.timeout) + + let contentView = try await ContentView().hostAndInspect(with: \.inspection) + let wf1 = try contentView.tabView().view(MapWorkflow.self, 0).actualView() + XCTAssertEqual(try contentView.tabView().view(MapWorkflow.self, 0).tabItem().label().title().text().string(), "Map") + let wf2 = try contentView.tabView().view(QRScannerWorkflow.self, 1).actualView() + XCTAssertEqual(try contentView.tabView().view(QRScannerWorkflow.self, 1).tabItem().label().title().text().string(), "QR Scanner") + let wf3 = try contentView.tabView().view(ProfileWorkflow.self, 2).actualView() + XCTAssertEqual(try contentView.tabView().view(ProfileWorkflow.self, 2).tabItem().label().title().text().string(), "Profile") + + let wfr1 = try await wf1.hostAndInspect(with: \.inspection) + try await wfr1.find(MapFeatureOnboardingView.self).proceedInWorkflow() + XCTAssertNoThrow(try wfr1.find(MapFeatureView.self)) + + let wfr2 = try await wf2.hostAndInspect(with: \.inspection) + try await wfr2.find(QRScannerFeatureOnboardingView.self).proceedInWorkflow() + XCTAssertNoThrow(try wfr2.find(QRScannerFeatureView.self)) + + let wfr3 = try await wf3.hostAndInspect(with: \.inspection) + try await wfr3.find(ProfileFeatureOnboardingView.self).proceedInWorkflow() + XCTAssertNoThrow(try wfr3.find(ProfileFeatureView.self)) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/GenericOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/GenericOnboardingViewTests.swift index 360abe6d9..803a512e8 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/GenericOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/GenericOnboardingViewTests.swift @@ -26,21 +26,26 @@ final class GenericOnboardingViewTests: XCTestCase, View { Container.default.removeAll() } - func testOnboardingInWorkflow() throws { + func testOnboardingInWorkflow() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(false, forKey: defaultModel.appStorageKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: defaultModel) { - thenProceed(with: GenericOnboardingView.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Text.self)) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), self.defaultModel.featureTitle) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: defaultModel) { + thenProceed(with: GenericOnboardingView.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try launcher.find(ViewType.Text.self)) + XCTAssertEqual(try launcher.find(ViewType.Text.self).string(), self.defaultModel.featureTitle) + XCTAssertNoThrow(try launcher.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } func testOnboardingViewLoads_WhenNoValueIsInUserDefaults() throws { @@ -64,22 +69,23 @@ final class GenericOnboardingViewTests: XCTestCase, View { XCTAssertFalse(GenericOnboardingView(with: defaultModel).shouldLoad(), "Profile onboarding should not show if default is true") } - func testOnboardingAsView() throws { + func testOnboardingAsView() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(true, forKey: defaultModel.appStorageKey) Container.default.register(UserDefaults.self) { _ in defaults } let onboardingActionExpectation = expectation(description: "View Proceeded") - let genericOnboardingView = GenericOnboardingView(model: defaultModel) { - onboardingActionExpectation.fulfill() + let onboarding = try await MainActor.run { + GenericOnboardingView(model: defaultModel) { + onboardingActionExpectation.fulfill() + } } + .hostAndInspect(with: \.inspection) - let exp = ViewHosting.loadView(genericOnboardingView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Text.self)) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), self.defaultModel.featureTitle) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - } + XCTAssertNoThrow(try onboarding.find(ViewType.Text.self)) + XCTAssertEqual(try onboarding.find(ViewType.Text.self).string(), self.defaultModel.featureTitle) + XCTAssertNoThrow(try onboarding.find(ViewType.Button.self).tap()) - wait(for: [exp, onboardingActionExpectation], timeout: TestConstant.timeout) + wait(for: [onboardingActionExpectation], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/LoginTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/LoginTests.swift index f095aa79b..0af34dc1b 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/LoginTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/LoginTests.swift @@ -28,40 +28,39 @@ final class LoginTests: XCTestCase, View, WorkflowTestingReceiver { Self.workflowLaunchedData.removeAll() } - func testBasicLayout() { - let exp = ViewHosting.loadView(LoginView()).inspection.inspect { view in - XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 1) - XCTAssertEqual(view.findAll(ViewType.SecureField.self).count, 1) - XCTAssertNoThrow(try view.findLoginButton()) - XCTAssertNoThrow(try view.findSignUpButton()) - } - wait(for: [exp], timeout: TestConstant.timeout) + func testBasicLayout() async throws { + let view = try await LoginView().hostAndInspect(with: \.inspection) + + XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 1) + XCTAssertEqual(view.findAll(ViewType.SecureField.self).count, 1) + XCTAssertNoThrow(try view.findLoginButton()) + XCTAssertNoThrow(try view.findSignUpButton()) } - func testLoginProceedsWorkflow() { + func testLoginProceedsWorkflow() async throws { let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: LoginView.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.findLoginButton().tap()) + let view = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: LoginView.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try view.findLoginButton().tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } - func testSignupCorrectlyLaunchesSignupWorkflow() throws { + func testSignupCorrectlyLaunchesSignupWorkflow() async throws { Self.workflowLaunchedData.removeAll() - var loginView: InspectableView<ViewType.View<LoginView>>! - let exp = ViewHosting.loadView(LoginView()).inspection.inspect { view in - loginView = view - XCTAssertFalse(try view.actualView().showSignUp) - XCTAssertNoThrow(try view.findSignUpButton().tap()) - XCTAssert(try view.actualView().showSignUp) - } - wait(for: [exp], timeout: TestConstant.timeout) + let loginView = try await LoginView().hostAndInspect(with: \.inspection) - XCTAssertNotNil(loginView) + XCTAssertFalse(try loginView.actualView().showSignUp) + XCTAssertNoThrow(try loginView.findSignUpButton().tap()) + XCTAssert(try loginView.actualView().showSignUp) waitUntil(Self.workflowTestingData != nil) let data = Self.workflowTestingData @@ -76,11 +75,8 @@ final class LoginTests: XCTestCase, View, WorkflowTestingReceiver { // Complete workflow (Self.workflowTestingData?.orchestrationResponder as? WorkflowViewModel)?.onFinishPublisher.send(AnyWorkflow.PassedArgs.none) - wait(for: [ - ViewHosting.loadView(try loginView.actualView()).inspection.inspect { view in - XCTAssertFalse(try view.actualView().showSignUp) - } - ], timeout: TestConstant.timeout) + let view = try await loginView.actualView().hostAndInspect(with: \.inspection) + XCTAssertFalse(try view.actualView().showSignUp) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MFAViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MFAViewTests.swift index 7c91175db..acf6d0f28 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MFAViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MFAViewTests.swift @@ -13,36 +13,33 @@ import ViewInspector @testable import SwiftUIExample final class MFAViewTests: XCTestCase { - func testMFAView() throws { - let exp = ViewHosting.loadView(MFAView(with: .none)).inspection.inspect { view in - XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), - "This is your friendly MFA Assistant! Tap the button below to pretend to send a push notification and require an account code") - XCTAssertEqual(try view.find(ViewType.Button.self).labelView().text().string(), "Start MFA") - } - wait(for: [exp], timeout: TestConstant.timeout) + func testMFAView() async throws { + let view = try await MFAView(with: .none).hostAndInspect(with: \.inspection) + + XCTAssertEqual(try view.find(ViewType.Text.self, traversal: .depthFirst).string(), + "This is your friendly MFA Assistant! Tap the button below to pretend to send a push notification and require an account code") + XCTAssertEqual(try view.find(ViewType.Button.self).labelView().text().string(), "Start MFA") } - func testMFAViewAllowsCodeInput() throws { - let exp = ViewHosting.loadView(MFAView(with: .none)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") - XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1111")) - } - wait(for: [exp], timeout: TestConstant.timeout) + func testMFAViewAllowsCodeInput() async throws { + let view = try await MFAView(with: .none).hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") + XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1111")) } - func testMFAViewShowsAlertWhenCodeIsWrong() throws { - let exp = ViewHosting.loadView(MFAView(with: .none)).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") - XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1111")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssertEqual(try view.find(ViewType.Alert.self).title().string(), "Invalid code entered, abandoning workflow.") - } - wait(for: [exp], timeout: TestConstant.timeout) + func testMFAViewShowsAlertWhenCodeIsWrong() async throws { + let view = try await MFAView(with: .none).hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") + XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1111")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssertEqual(try view.find(ViewType.Alert.self).title().string(), "Invalid code entered, abandoning workflow.") } - func testMFAViewViewProceedsWithCorrectDataWhenCorrectMFACodeEntered() { + func testMFAViewViewProceedsWithCorrectDataWhenCorrectMFACodeEntered() async throws { class CustomObj { } let ref = CustomObj() let proceedCalled = expectation(description: "Proceed called") @@ -54,12 +51,14 @@ final class MFAViewTests: XCTestCase { proceedCalled.fulfill() } mfaView._workflowPointer = erased - let exp = ViewHosting.loadView(mfaView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") - XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1234")) - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) - } - wait(for: [exp, proceedCalled], timeout: TestConstant.timeout) + + let view = try await mfaView.hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Code (enter 1234 to proceed)") + XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput("1234")) + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + + wait(for: [proceedCalled], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift index 93009de85..b54cca0f7 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureOnboardingViewTests.swift @@ -20,21 +20,27 @@ final class MapFeatureOnboardingViewTests: XCTestCase, View { Container.default.removeAll() } - func testOnboardingInWorkflow() throws { + func testOnboardingInWorkflow() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: MapFeatureOnboardingView.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Text.self)) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Learn about our awesome map feature!") - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + + let view = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: MapFeatureOnboardingView.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try view.find(ViewType.Text.self)) + XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Learn about our awesome map feature!") + XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } func testOnboardingViewLoads_WhenNoValueIsInUserDefaults() throws { diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureViewTests.swift index dd54456b9..b5097af0b 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/MapFeatureViewTests.swift @@ -12,13 +12,12 @@ import ViewInspector @testable import SwiftUIExample final class MapFeatureViewTests: XCTestCase { - func testMapFeatureView() throws { - let exp = ViewHosting.loadView(MapFeatureView()).inspection.inspect { view in - let map = try view.map() - let region = try map.coordinateRegion() - XCTAssertEqual(region.center.latitude, 38.70196, accuracy: 0.9) // swiftlint:disable:this number_separator - XCTAssertEqual(region.center.longitude, -90.44906, accuracy: 0.9) // swiftlint:disable:this number_separator - } - wait(for: [exp], timeout: TestConstant.timeout) + func testMapFeatureView() async throws { + let view = try await MapFeatureView().hostAndInspect(with: \.inspection) + + let map = try view.map() + let region = try map.coordinateRegion() + XCTAssertEqual(region.center.latitude, 38.70196, accuracy: 0.9) // swiftlint:disable:this number_separator + XCTAssertEqual(region.center.longitude, -90.44906, accuracy: 0.9) // swiftlint:disable:this number_separator } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/PasswordFieldTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/PasswordFieldTests.swift index 581c87b92..4d1206048 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/PasswordFieldTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/PasswordFieldTests.swift @@ -13,53 +13,45 @@ import ViewInspector @testable import SwiftUIExample class PasswordFieldTests: XCTestCase { - func testRevealButtonTogglesLayout() { - let passwordField = PasswordField(password: Binding<String>(wrappedValue: "")) + func testRevealButtonTogglesLayout() async throws { + let passwordField = try await PasswordField(password: Binding<String>(wrappedValue: "")).hostAndInspect(with: \.inspection) - let exp = ViewHosting.loadView(passwordField).inspection.inspect { view in - XCTAssertEqual(view.findAll(ViewType.Button.self).count, 1) - XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 0) - XCTAssertEqual(view.findAll(ViewType.SecureField.self).count, 1) + XCTAssertEqual(passwordField.findAll(ViewType.Button.self).count, 1) + XCTAssertEqual(passwordField.findAll(ViewType.TextField.self).count, 0) + XCTAssertEqual(passwordField.findAll(ViewType.SecureField.self).count, 1) - try view.find(ViewType.Button.self).tap() - XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 1) - XCTAssertEqual(view.findAll(ViewType.SecureField.self).count, 0) + try passwordField.find(ViewType.Button.self).tap() + XCTAssertEqual(passwordField.findAll(ViewType.TextField.self).count, 1) + XCTAssertEqual(passwordField.findAll(ViewType.SecureField.self).count, 0) - try view.find(ViewType.Button.self).tap() - XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 0) - XCTAssertEqual(view.findAll(ViewType.SecureField.self).count, 1) - } - - wait(for: [exp], timeout: TestConstant.timeout) + try passwordField.find(ViewType.Button.self).tap() + XCTAssertEqual(passwordField.findAll(ViewType.TextField.self).count, 0) + XCTAssertEqual(passwordField.findAll(ViewType.SecureField.self).count, 1) } - func testPasswordIsBoundBetweenStates() { + func testPasswordIsBoundBetweenStates() async throws { let password = Binding<String>(wrappedValue: "initial password") let expectedPassword = "This is the updated password" - let passwordField = PasswordField(showPassword: true, password: password) - - let exp = ViewHosting.loadView(passwordField).inspection.inspect { view in - let textField = try view.find(ViewType.TextField.self) - XCTAssertEqual(try textField.input(), try view.actualView().password) - XCTAssertEqual(password.wrappedValue, try view.actualView().password) - - try textField.setInput(expectedPassword) - XCTAssertEqual(try textField.input(), expectedPassword) - XCTAssertEqual(try view.actualView().password, expectedPassword) - XCTAssertEqual(password.wrappedValue, expectedPassword) - - try view.find(ViewType.Button.self).tap() - let secureField = try view.find(ViewType.SecureField.self) - XCTAssertEqual(try secureField.input(), try view.actualView().password) - XCTAssertEqual(try view.actualView().password, expectedPassword) - XCTAssertEqual(password.wrappedValue, expectedPassword) - - try secureField.setInput("") - XCTAssertEqual(try secureField.input(), "") - XCTAssertEqual(try view.actualView().password, "") - XCTAssertEqual(password.wrappedValue, "") - } - - wait(for: [exp], timeout: TestConstant.timeout) + let passwordField = try await PasswordField(showPassword: true, password: password).hostAndInspect(with: \.inspection) + + let textField = try passwordField.find(ViewType.TextField.self) + XCTAssertEqual(try textField.input(), try passwordField.actualView().password) + XCTAssertEqual(password.wrappedValue, try passwordField.actualView().password) + + try textField.setInput(expectedPassword) + XCTAssertEqual(try textField.input(), expectedPassword) + XCTAssertEqual(try passwordField.actualView().password, expectedPassword) + XCTAssertEqual(password.wrappedValue, expectedPassword) + + try passwordField.find(ViewType.Button.self).tap() + let secureField = try passwordField.find(ViewType.SecureField.self) + XCTAssertEqual(try secureField.input(), try passwordField.actualView().password) + XCTAssertEqual(try passwordField.actualView().password, expectedPassword) + XCTAssertEqual(password.wrappedValue, expectedPassword) + + try secureField.setInput("") + XCTAssertEqual(try secureField.input(), "") + XCTAssertEqual(try passwordField.actualView().password, "") + XCTAssertEqual(password.wrappedValue, "") } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift index f95c07413..32c7f90da 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/ProfileFeatureOnboardingViewTests.swift @@ -20,21 +20,25 @@ final class ProfileFeatureOnboardingViewTests: XCTestCase, View { Container.default.removeAll() } - func testOnboardingInWorkflow() throws { + func testOnboardingInWorkflow() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: ProfileFeatureOnboardingView.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Text.self)) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Welcome to our new profile management feature!") - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: ProfileFeatureOnboardingView.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + + XCTAssertNoThrow(try launcher.find(ViewType.Text.self)) + XCTAssertEqual(try launcher.find(ViewType.Text.self).string(), "Welcome to our new profile management feature!") + XCTAssertNoThrow(try launcher.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } func testOnboardingViewLoads_WhenNoValueIsInUserDefaults() throws { diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift index 31d1d6025..fc2e8c16e 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScannerFeatureOnboardingViewTests.swift @@ -20,21 +20,26 @@ final class QRScannerFeatureOnboardingViewTests: XCTestCase, View { Container.default.removeAll() } - func testOnboardingInWorkflow() throws { + func testOnboardingInWorkflow() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: QRScannerFeatureOnboardingView.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Text.self)) - XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Learn about our awesome QR scanning feature!") - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: QRScannerFeatureOnboardingView.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try launcher.find(ViewType.Text.self)) + XCTAssertEqual(try launcher.find(ViewType.Text.self).string(), "Learn about our awesome QR scanning feature!") + XCTAssertNoThrow(try launcher.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } func testOnboardingViewLoads_WhenNoValueIsInUserDefaults() throws { diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScanningViewTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScanningViewTests.swift index 13a129ba1..a0ac9b234 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScanningViewTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/QRScanningViewTests.swift @@ -16,21 +16,23 @@ import CodeScanner @testable import SwiftUIExample final class QRScanningViewTests: XCTestCase { - func testQRScanningView() throws { - let exp = ViewHosting.loadView(QRScannerFeatureView()).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.view(CodeScannerView.self).actualView().codeTypes, [.qr]) + func testQRScanningView() async throws { + let view = try await QRScannerFeatureView().hostAndInspect(with: \.inspection) + + try await MainActor.run { + XCTAssertEqual(try view.view(CodeScannerView.self).actualView().codeTypes, [.qr]) } - wait(for: [exp], timeout: TestConstant.timeout) } - func testQRScanningView_ShowsSheetWhenScanCompletes() throws { + func testQRScanningView_ShowsSheetWhenScanCompletes() async throws { let code = UUID().uuidString - let exp = ViewHosting.loadView(QRScannerFeatureView()).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.view(CodeScannerView.self).actualView().completion(.success(code))) - XCTAssertEqual(try viewUnderTest.view(CodeScannerView.self).sheet().find(ViewType.Text.self).string(), "SCANNED DATA: \(code)") - XCTAssertNoThrow(try viewUnderTest.view(CodeScannerView.self).sheet().callOnDismiss()) - XCTAssertThrowsError(try viewUnderTest.view(CodeScannerView.self).sheet()) + let view = try await QRScannerFeatureView().hostAndInspect(with: \.inspection) + + try await MainActor.run { + XCTAssertNoThrow(try view.view(CodeScannerView.self).actualView().completion(.success(code))) + XCTAssertEqual(try view.view(CodeScannerView.self).sheet().find(ViewType.Text.self).string(), "SCANNED DATA: \(code)") + XCTAssertNoThrow(try view.view(CodeScannerView.self).sheet().dismiss()) + XCTAssertThrowsError(try view.view(CodeScannerView.self).sheet()) } - wait(for: [exp], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SignUpTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SignUpTests.swift index 92300fc1f..aa2782896 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SignUpTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SignUpTests.swift @@ -14,25 +14,28 @@ import ViewInspector @testable import SwiftUIExample final class SignUpTests: XCTestCase, View { - func testBasicLayout() { - let exp = ViewHosting.loadView(SignUp()).inspection.inspect { view in - XCTAssertEqual(view.findAll(PasswordField.self).count, 2, "2 password fields needed") - XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 1, "1 username field needed") - XCTAssertNoThrow(try view.findProceedButton(), "proceed button needed") - } - wait(for: [exp], timeout: TestConstant.timeout) + func testBasicLayout() async throws { + let view = try await SignUp().hostAndInspect(with: \.inspection) + + XCTAssertEqual(view.findAll(PasswordField.self).count, 2, "2 password fields needed") + XCTAssertEqual(view.findAll(ViewType.TextField.self).count, 1, "1 username field needed") + XCTAssertNoThrow(try view.findProceedButton(), "proceed button needed") } - func testContinueProceedsWorkflow() { + func testContinueProceedsWorkflow() async throws { let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: SignUp.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.findProceedButton().tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: SignUp.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try launcher.findProceedButton().tap()) + wait(for: [workflowFinished], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SwiftCurrentOnboardingTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SwiftCurrentOnboardingTests.swift index 7f4698b43..6f0c35c7c 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SwiftCurrentOnboardingTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/SwiftCurrentOnboardingTests.swift @@ -20,19 +20,24 @@ final class SwiftCurrentOnboardingTests: XCTestCase, View { Container.default.removeAll() } - func testOnboardingInWorkflow() throws { + func testOnboardingInWorkflow() async throws { let defaults = try XCTUnwrap(UserDefaults(suiteName: #function)) defaults.set(false, forKey: defaultsKey) Container.default.register(UserDefaults.self) { _ in defaults } let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: SwiftCurrentOnboarding.self) - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: SwiftCurrentOnboarding.self) + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + XCTAssertNoThrow(try launcher.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) XCTAssert(defaults.bool(forKey: defaultsKey)) } diff --git a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/TermsAndConditionsTests.swift b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/TermsAndConditionsTests.swift index 436e2e77b..f311999cd 100644 --- a/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/TermsAndConditionsTests.swift +++ b/ExampleApps/SwiftUIExample/SwiftUIExampleTests/Views/TermsAndConditionsTests.swift @@ -14,42 +14,51 @@ import ViewInspector @testable import SwiftUIExample final class TermsAndConditionsTests: XCTestCase, View { - func testLayout() { - let exp = ViewHosting.loadView(TermsAndConditions()).inspection.inspect { view in - XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2) - } - wait(for: [exp], timeout: TestConstant.timeout) + func testLayout() async throws { + let view = try await TermsAndConditions().hostAndInspect(with: \.inspection) + + XCTAssertEqual(view.findAll(ViewType.Button.self).count, 2) } - func testPrimaryAcceptButtonCompletesWorkflow() { + func testPrimaryAcceptButtonCompletesWorkflow() async throws { let workflowFinished = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: TermsAndConditions.self) - }.onAbandon { - XCTFail("Abandon should not have been called") - }.onFinish { _ in - workflowFinished.fulfill() - }).inspection.inspect { view in - let primaryButton = try view.find(PrimaryButton.self) // ToS should have a primary call to accept - XCTAssertEqual(try primaryButton.find(ViewType.Text.self).string(), "Accept") - XCTAssertNoThrow(try primaryButton.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: TermsAndConditions.self) + }.onAbandon { + XCTFail("Abandon should not have been called") + }.onFinish { _ in + workflowFinished.fulfill() + } } - wait(for: [exp, workflowFinished], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let primaryButton = try launcher.find(PrimaryButton.self) // ToS should have a primary call to accept + XCTAssertEqual(try primaryButton.find(ViewType.Text.self).string(), "Accept") + XCTAssertNoThrow(try primaryButton.find(ViewType.Button.self).tap()) + + wait(for: [workflowFinished], timeout: TestConstant.timeout) } - func testSecondaryRejectButtonAbandonsWorkflow() { + func testSecondaryRejectButtonAbandonsWorkflow() async throws { let workflowAbandoned = expectation(description: "View Proceeded") - let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: TermsAndConditions.self) - }.onAbandon { - workflowAbandoned.fulfill() - }.onFinish { _ in - XCTFail("Complete should not have been called") - }).inspection.inspect { view in - let secondaryButton = try view.find(SecondaryButton.self) // ToS sould have a secondary call to decline - XCTAssertEqual(try secondaryButton.find(ViewType.Text.self).string(), "Decline") - XCTAssertNoThrow(try secondaryButton.find(ViewType.Button.self).tap()) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: TermsAndConditions.self) + }.onAbandon { + workflowAbandoned.fulfill() + }.onFinish { _ in + XCTFail("Complete should not have been called") + } } - wait(for: [exp, workflowAbandoned], timeout: TestConstant.timeout) + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let secondaryButton = try launcher.find(SecondaryButton.self) // ToS sould have a secondary call to decline + XCTAssertEqual(try secondaryButton.find(ViewType.Text.self).string(), "Decline") + XCTAssertNoThrow(try secondaryButton.find(ViewType.Button.self).tap()) + + wait(for: [workflowAbandoned], timeout: TestConstant.timeout) } } diff --git a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/TestUtilities/TopViewController.swift b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/TestUtilities/TopViewController.swift index 945e2e46d..781755d44 100644 --- a/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/TestUtilities/TopViewController.swift +++ b/ExampleApps/UIKitExample/SwiftCurrent_UIKitTests/TestUtilities/TopViewController.swift @@ -10,7 +10,11 @@ import Foundation import UIKit extension UIApplication { - static func topViewController(of controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? { + static var rootViewController: UIViewController? { + UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first?.windows.first?.rootViewController + } + + static func topViewController(of controller: UIViewController? = rootViewController) -> UIViewController? { if let navigationController = controller as? UINavigationController, let visible = navigationController.visibleViewController { return topViewController(of: visible) diff --git a/Package.swift b/Package.swift index 782bdcec9..e48d0a0de 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( .package(url: "https://github.com/mattgallagher/CwlCatchException.git", from: Version("2.0.0-beta.1")), .package(url: "https://github.com/apple/swift-algorithms", .upToNextMajor(from: "0.0.1")), .package(url: "https://github.com/sindresorhus/ExceptionCatcher", from: "2.0.0"), - .package(url: "https://github.com/nalexn/ViewInspector.git", from: "0.9.0") + .package(url: "https://github.com/nalexn/ViewInspector.git", from: "0.9.1") ], targets: [ .target( diff --git a/SwiftCurrent.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SwiftCurrent.xcworkspace/xcshareddata/swiftpm/Package.resolved index cbac56caa..e32b92764 100644 --- a/SwiftCurrent.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/SwiftCurrent.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -78,8 +78,8 @@ "repositoryURL": "https://github.com/nalexn/ViewInspector.git", "state": { "branch": null, - "revision": "0d546878902bdde8c4682141f848a9dc44e04ba6", - "version": "0.9.0" + "revision": "6b88c4ec1fa20cf38f2138052e63c8e79df5d76e", + "version": "0.9.1" } } ] diff --git a/Tests/SwiftCurrent_SwiftUITests/AnyFlowRepresentableViewTests.swift b/Tests/SwiftCurrent_SwiftUITests/AnyFlowRepresentableViewTests.swift index e731e150a..8964a7897 100644 --- a/Tests/SwiftCurrent_SwiftUITests/AnyFlowRepresentableViewTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/AnyFlowRepresentableViewTests.swift @@ -15,10 +15,6 @@ import SwiftCurrent @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class AnyFlowRepresentableViewTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - func testAnyFlowRepresentableViewDoesNotCreate_StrongRetainCycle() { var afrv: AnyFlowRepresentableView? weak var ref: AnyFlowRepresentableView? diff --git a/Tests/SwiftCurrent_SwiftUITests/AnyWorkflowTests.swift b/Tests/SwiftCurrent_SwiftUITests/AnyWorkflowTests.swift index f8d2f0bca..7c2db3d48 100644 --- a/Tests/SwiftCurrent_SwiftUITests/AnyWorkflowTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/AnyWorkflowTests.swift @@ -14,10 +14,6 @@ import SwiftCurrent_SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class AnyWorkflowTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - func testAbandonDoesNotBLOWUP() { let wf = Workflow(FR.self) AnyWorkflow(wf).abandon() diff --git a/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift b/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift index 7c459b028..989cb32a4 100644 --- a/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/GenericConstraintTests.swift @@ -31,33 +31,28 @@ extension FlowRepresentable { } } -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class GenericConstraintTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - // MARK: Generic Initializer Tests // MARK: Input Type == Never - func testWhenInputIsNever_WorkflowCanLaunchWithArguments() throws { + func testWhenInputIsNever_WorkflowCanLaunchWithArguments() async throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: Optional("Discarded arguments")) { - thenProceed(with: FR1.self) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: Optional("Discarded arguments")) { + thenProceed(with: FR1.self) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self)) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertNoThrow(try workflowView.find(FR1.self)) } - func testWhenInputIsNeverAndViewDoesNotLoad_WorkflowCanLaunchWithArgumentsAndArgumentsArePassedToTheNextFR() throws { + func testWhenInputIsNeverAndViewDoesNotLoad_WorkflowCanLaunchWithArgumentsAndArgumentsArePassedToTheNextFR() async throws { struct FR1: View, FlowRepresentable, Inspectable { typealias WorkflowOutput = String weak var _workflowPointer: AnyFlowRepresentable? @@ -72,68 +67,67 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgument = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgument) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgument) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR2.self).actualView().input, expectedArgument) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR2.self).actualView().input, expectedArgument) } - func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() { + func testWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testWhenInputIsNever_PresentationTypeCanBeSetWithAutoclosure() { + func testWhenInputIsNever_PresentationTypeCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().presentationType, .navigationLink) } - func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() { + func testWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).persistence { - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self).persistence { + defer { expectation.fulfill() } + return .removedAfterProceeding + } } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectation, expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -142,21 +136,19 @@ final class GenericConstraintTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.actualView().getWrappedView() + XCTAssertNoThrow(try workflowView.find(type(of: view)).find(FR2.self)) } - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -165,22 +157,21 @@ final class GenericConstraintTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -189,23 +180,21 @@ final class GenericConstraintTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -215,21 +204,20 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: AnyWorkflow.PassedArgs) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -239,22 +227,21 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: AnyWorkflow.PassedArgs) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -264,23 +251,21 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: AnyWorkflow.PassedArgs) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -291,21 +276,20 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: Int) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -316,22 +300,21 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: Int) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -342,24 +325,23 @@ final class GenericConstraintTests: XCTestCase, View { var body: some View { Text(String(describing: Self.self)) } init(with args: Int) { } } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { .removedAfterProceeding } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { .removedAfterProceeding } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } // MARK: Input Type == AnyWorkflow.PassedArgs - func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() { + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -367,17 +349,16 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testWhenInputIsAnyWorkflowPassedArgs_PresentationTypeCanBeSetWithAutoclosure() { + func testWhenInputIsAnyWorkflowPassedArgs_PresentationTypeCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -385,17 +366,16 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().presentationType, .navigationLink) } - func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() { + func testWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -404,20 +384,21 @@ final class GenericConstraintTests: XCTestCase, View { let expectedArgs = UUID().uuidString let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -429,21 +410,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -455,22 +435,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -482,26 +461,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -514,21 +491,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -541,22 +517,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -569,25 +544,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -601,21 +575,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -629,22 +602,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -658,27 +630,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } // MARK: Input Type == Concrete Type - func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() { + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -686,17 +656,16 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testWhenInputIsConcreteType_PresentationTypeCanBeSetWithAutoclosure() { + func testWhenInputIsConcreteType_PresentationTypeCanBeSetWithAutoclosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -704,17 +673,16 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).presentationType(.navigationLink) - } + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).presentationType(.navigationLink) + } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().presentationType, .navigationLink) - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().presentationType, .navigationLink) } - func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() { + func testWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -723,21 +691,21 @@ final class GenericConstraintTests: XCTestCase, View { let expectedArgs = UUID().uuidString let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -749,21 +717,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -775,22 +742,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -802,25 +768,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -833,21 +798,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -860,22 +824,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -888,25 +851,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -920,21 +882,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -948,22 +909,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -977,25 +937,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = String var _workflowPointer: AnyFlowRepresentable? @@ -1009,22 +968,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await workflowView.find(FR1.self).proceedInWorkflow("") + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = String var _workflowPointer: AnyFlowRepresentable? @@ -1038,22 +995,21 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence(.removedAfterProceeding) - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence(.removedAfterProceeding) } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow("") + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR1: FlowRepresentable, View, Inspectable { typealias WorkflowOutput = String var _workflowPointer: AnyFlowRepresentable? @@ -1067,29 +1023,28 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0, expectedArgs) - return .removedAfterProceeding - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0, expectedArgs) + return .removedAfterProceeding + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await workflowView.find(FR1.self).proceedInWorkflow("") + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } // MARK: Generic Proceed Tests // MARK: Input Type == Never - func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() throws { + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1100,21 +1055,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() throws { + func testProceedingWhenInputIsNever_FlowPersistenceCanBeSetWithClosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1126,24 +1080,24 @@ final class GenericConstraintTests: XCTestCase, View { let expectedArgs = UUID().uuidString let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + defer { expectation.fulfill() } + return .removedAfterProceeding + } } } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1158,26 +1112,26 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1192,27 +1146,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() throws { + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnotherNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1227,27 +1179,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { .removedAfterProceeding } + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1263,26 +1213,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1298,27 +1246,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1334,27 +1280,26 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsNeverWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1371,26 +1316,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsNeverWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1407,27 +1350,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsNeverWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1444,34 +1385,31 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } } } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow(1) + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } // MARK: Input Type == AnyWorkflow.PassedArgs - func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1483,21 +1421,19 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + XCTAssertEqual(try workflowView.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgs_FlowPersistenceCanBeSetWithClosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1510,25 +1446,25 @@ final class GenericConstraintTests: XCTestCase, View { let expectedArgs = UUID().uuidString let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } } } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1544,26 +1480,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1579,27 +1513,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let workflowItem = try await workflowView.extractWrappedWorkflowItem() + try await workflowItem.find(FR1.self).proceedInWorkflow() + let view = try await workflowItem.extractWrappedWorkflowItem() + XCTAssertNoThrow(try view.find(FR2.self)) + XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1615,32 +1547,28 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - }.persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - try view.actualView().inspect { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + }.persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try wfr1.find(FR1.self).actualView().persistence, .removedAfterProceeding) + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1657,26 +1585,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1693,27 +1619,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1730,31 +1654,28 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedArgs) + return .removedAfterProceeding + } } } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1772,26 +1693,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1809,27 +1728,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsAnyWorkflowPassedArgsWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1847,28 +1764,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } } } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } // MARK: Input Type == Concrete Type @@ -1893,7 +1807,7 @@ final class GenericConstraintTests: XCTestCase, View { } } - func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() throws { + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithAutoclosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1905,21 +1819,20 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence(.removedAfterProceeding) - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence(.removedAfterProceeding) + } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() throws { + func testProceedingWhenInputIsConcreteType_FlowPersistenceCanBeSetWithClosure() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1932,26 +1845,25 @@ final class GenericConstraintTests: XCTestCase, View { let expectedArgs = UUID().uuidString let expectation = self.expectation(description: "FlowPersistence closure called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self).persistence { - XCTAssertEqual($0, expectedArgs) - defer { expectation.fulfill() } - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self).persistence { + XCTAssertEqual($0, expectedArgs) + defer { expectation.fulfill() } + return .removedAfterProceeding + } } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) - } - } - wait(for: [expectViewLoaded, expectation], timeout: TestConstant.timeout) + try await workflowView.find(FR0.self).proceedInWorkflow() + let view = try await workflowView.extractWrappedWorkflowItem() + XCTAssertEqual(try view.find(FR1.self).actualView().persistence, .removedAfterProceeding) + wait(for: [expectation], timeout: TestConstant.timeout) } - func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -1967,26 +1879,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2002,27 +1912,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2038,27 +1946,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { .removedAfterProceeding } + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsConcreteTypeWithDefaultFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2075,26 +1981,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2111,27 +2015,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2148,27 +2050,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2186,26 +2086,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2223,27 +2121,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToADifferentInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2261,31 +2157,28 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { - XCTAssertEqual($0, 1) - return .removedAfterProceeding + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { + XCTAssertEqual($0, 1) + return .removedAfterProceeding + } } } } - } + }.hostAndInspect(with: \.inspection).extractWorkflowItem() - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(1)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) - } - } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(1) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeArgsWithDefaultFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2303,26 +2196,24 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow("") + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) } - func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeWithAutoclosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2340,27 +2231,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow("") + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() throws { + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToTheSameInputTypeItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2378,27 +2267,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence { _ in .removedAfterProceeding } + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow("") + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() throws { + func testProceedingWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToAnyWorkflowPassedArgsItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2416,27 +2303,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(expectedArgs) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testProceedingTwiceWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() throws { + func testProceedingTwiceWhenInputIsConcreteTypeWithClosureFlowPersistence_WorkflowCanProceedToNeverItem() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2453,27 +2338,25 @@ final class GenericConstraintTests: XCTestCase, View { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow(expectedArgs)) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow(expectedArgs) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnView() { + func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnView() async throws { final class FR0: UIViewController, FlowRepresentable { weak var _workflowPointer: AnyFlowRepresentable? } @@ -2486,33 +2369,27 @@ final class GenericConstraintTests: XCTestCase, View { weak var _workflowPointer: AnyFlowRepresentable? } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR0>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR1>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR2>.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(ViewControllerWrapper<FR0>.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(ViewControllerWrapper<FR1>.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(ViewControllerWrapper<FR2>.self)) } } -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15, macOS 11, tvOS 14.0, watchOS 7.0, *) final class ThenProceedOnAppTests: XCTestCase, App { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testThenProceedFunctionsAsExpectedOnApp() { + func testThenProceedFunctionsAsExpectedOnApp() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2530,27 +2407,25 @@ final class ThenProceedOnAppTests: XCTestCase, App { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow("") + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnApp() { + func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnApp() async throws { final class FR0: UIViewController, FlowRepresentable { weak var _workflowPointer: AnyFlowRepresentable? } @@ -2563,33 +2438,27 @@ final class ThenProceedOnAppTests: XCTestCase, App { weak var _workflowPointer: AnyFlowRepresentable? } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR0>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR1>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR2>.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(ViewControllerWrapper<FR0>.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(ViewControllerWrapper<FR1>.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(ViewControllerWrapper<FR2>.self)) } } -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class ThenProceedOnSceneTests: XCTestCase, Scene { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testThenProceedFunctionsAsExpectedOnScene() { + func testThenProceedFunctionsAsExpectedOnScene() async throws { struct FR0: PassthroughFlowRepresentable, View, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text(String(describing: Self.self)) } @@ -2607,27 +2476,25 @@ final class ThenProceedOnSceneTests: XCTestCase, Scene { } let expectedArgs = UUID().uuidString - let workflowView = WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self).persistence(.removedAfterProceeding) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(FR0.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR1.self).actualView().proceedInWorkflow("")) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(FR2.self)) - XCTAssertEqual(try view.find(FR2.self).actualView().persistence, .removedAfterProceeding) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self).persistence(.removedAfterProceeding) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(FR0.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(FR1.self).proceedInWorkflow("") + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(FR2.self)) + XCTAssertEqual(try wfr2.find(FR2.self).actualView().persistence, .removedAfterProceeding) } - func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnScene() { + func testThenProceedFunctions_WithUIViewControllers_AsExpectedOnScene() async throws { final class FR0: UIViewController, FlowRepresentable { weak var _workflowPointer: AnyFlowRepresentable? } @@ -2640,22 +2507,20 @@ final class ThenProceedOnSceneTests: XCTestCase, Scene { weak var _workflowPointer: AnyFlowRepresentable? } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR0.self) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR0>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR1>.self).actualView().proceedInWorkflow()) - try view.actualView().inspectWrapped { view in - XCTAssertNoThrow(try view.find(ViewControllerWrapper<FR2>.self)) + let workflowView = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR0.self) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } } } - } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + }.hostAndInspect(with: \.inspection).extractWorkflowItem() + + try await workflowView.find(ViewControllerWrapper<FR0>.self).proceedInWorkflow() + let wfr1 = try await workflowView.extractWrappedWorkflowItem() + try await wfr1.find(ViewControllerWrapper<FR1>.self).proceedInWorkflow() + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr2.find(ViewControllerWrapper<FR2>.self)) } } diff --git a/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift b/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift index 15fe6153c..9d9a2e763 100644 --- a/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/PersistenceTests.swift @@ -13,14 +13,10 @@ import ViewInspector import SwiftCurrent @testable import SwiftCurrent_SwiftUI // testable sadly needed for inspection.inspect to work -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class PersistenceTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - // MARK: RemovedAfterProceedingTests - func testRemovedAfterProceeding_OnFirstItemInAWorkflow() throws { + func testRemovedAfterProceeding_OnFirstItemInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -37,7 +33,7 @@ final class PersistenceTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -48,24 +44,16 @@ final class PersistenceTests: XCTestCase, View { } .persistence(.removedAfterProceeding) } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR2.self).actualView().backUpInWorkflow()) - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR2.self).actualView().backUpInWorkflow()) + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - - func testRemovedAfterProceeding_OnMiddleItemInAWorkflow() throws { + + func testRemovedAfterProceeding_OnMiddleItemInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -82,7 +70,7 @@ final class PersistenceTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -93,32 +81,19 @@ final class PersistenceTests: XCTestCase, View { .persistence(.removedAfterProceeding) } } - ).inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().backUpInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } - } - } - } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).backUpInWorkflow() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - - func testRemovedAfterProceeding_OnLastItemInAWorkflow() throws { + + func testRemovedAfterProceeding_OnLastItemInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -136,7 +111,7 @@ final class PersistenceTests: XCTestCase, View { var body: some View { Text("FR4 type") } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -146,28 +121,23 @@ final class PersistenceTests: XCTestCase, View { } } } - .onFinish { _ in expectOnFinish.fulfill() } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr4.find(FR4.self)) - try fr3.actualView().inspect { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self)) - } - } - } + .onFinish { _ in + expectOnFinish.fulfill() + } - } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertNoThrow(try launcher.find(FR3.self)) - wait(for: [expectViewLoaded, expectOnFinish], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testRemovedAfterProceeding_OnMultipleItemsInAWorkflow() throws { + func testRemovedAfterProceeding_OnMultipleItemsInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -184,7 +154,7 @@ final class PersistenceTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -196,35 +166,19 @@ final class PersistenceTests: XCTestCase, View { .persistence(.removedAfterProceeding) } } - ).inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().backUpInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).backUpInWorkflow() + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - func testRemovedAfterProceeding_OnAllItemsInAWorkflow() throws { + func testRemovedAfterProceeding_OnAllItemsInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -243,7 +197,7 @@ final class PersistenceTests: XCTestCase, View { } let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: binding) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -256,38 +210,26 @@ final class PersistenceTests: XCTestCase, View { } .persistence(.removedAfterProceeding) } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertThrowsError(try fr4.find(FR4.self).actualView().backUpInWorkflow()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr4.find(FR4.self)) - try fr3.actualView().inspect { fr3 in - XCTAssertThrowsError(try fr3.find(FR3.self)) - try fr2.actualView().inspect { fr2 in - XCTAssertThrowsError(try fr2.find(FR2.self)) - try fr1.actualView().inspect { fr1 in - XCTAssertThrowsError(try fr1.find(FR1.self)) - XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - } - } - } - } - } - } - } + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self).actualView().backUpInWorkflow()) + try await launcher.find(FR4.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertThrowsError(try launcher.find(FR3.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } // MARK: Closure API Tests - func testPersistenceWorks_WhenDefinedFromAClosure() throws { + func testPersistenceWorks_WhenDefinedFromAClosure() async throws { struct FR1: View, FlowRepresentable, Inspectable { init(with args: String) { } var _workflowPointer: AnyFlowRepresentable? @@ -308,7 +250,7 @@ final class PersistenceTests: XCTestCase, View { let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") let expectedStart = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: binding, startingArgs: expectedStart) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -324,35 +266,24 @@ final class PersistenceTests: XCTestCase, View { return .removedAfterProceeding } } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertThrowsError(try fr4.find(FR4.self).actualView().backUpInWorkflow()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr4.find(FR4.self)) - try fr3.actualView().inspect { fr3 in - XCTAssertThrowsError(try fr3.find(FR3.self)) - try fr2.actualView().inspect { fr2 in - XCTAssertThrowsError(try fr2.find(FR2.self)) - try fr1.actualView().inspect { fr1 in - XCTAssertThrowsError(try fr1.find(FR1.self)) - XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - } - } - } - } - } - } - } - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self).actualView().backUpInWorkflow()) + try await launcher.find(FR4.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertThrowsError(try launcher.find(FR3.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testPersistenceWorks_WhenDefinedFromAClosure_AndItemHasInputOfPassedArgs() throws { + func testPersistenceWorks_WhenDefinedFromAClosure_AndItemHasInputOfPassedArgs() async throws { struct FR1: View, FlowRepresentable, Inspectable { init(with args: AnyWorkflow.PassedArgs) { } var _workflowPointer: AnyFlowRepresentable? @@ -373,7 +304,7 @@ final class PersistenceTests: XCTestCase, View { let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") let expectedStart = AnyWorkflow.PassedArgs.args(UUID().uuidString) - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: binding, startingArgs: expectedStart) { thenProceed(with: FR1.self) { @@ -391,36 +322,24 @@ final class PersistenceTests: XCTestCase, View { return .removedAfterProceeding } } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertThrowsError(try fr4.find(FR4.self).actualView().backUpInWorkflow()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr4.find(FR4.self)) - try fr3.actualView().inspect { fr3 in - XCTAssertThrowsError(try fr3.find(FR3.self)) - try fr2.actualView().inspect { fr2 in - XCTAssertThrowsError(try fr2.find(FR2.self)) - try fr1.actualView().inspect { fr1 in - XCTAssertThrowsError(try fr1.find(FR1.self)) - XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - } - } - } - } - } - } - } + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self).actualView().backUpInWorkflow()) + try await launcher.find(FR4.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertThrowsError(try launcher.find(FR3.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testPersistenceWorks_WhenDefinedFromAClosure_AndItemHasInputOfNever() throws { + func testPersistenceWorks_WhenDefinedFromAClosure_AndItemHasInputOfNever() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -439,10 +358,9 @@ final class PersistenceTests: XCTestCase, View { } let binding = Binding(wrappedValue: true) let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: binding) { thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) { thenProceed(with: FR3.self) { thenProceed(with: FR4.self).persistence(.removedAfterProceeding) @@ -453,33 +371,21 @@ final class PersistenceTests: XCTestCase, View { } .persistence { .removedAfterProceeding } } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertThrowsError(try fr4.find(FR4.self).actualView().backUpInWorkflow()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr4.find(FR4.self)) - try fr3.actualView().inspect { fr3 in - XCTAssertThrowsError(try fr3.find(FR3.self)) - try fr2.actualView().inspect { fr2 in - XCTAssertThrowsError(try fr2.find(FR2.self)) - try fr1.actualView().inspect { fr1 in - XCTAssertThrowsError(try fr1.find(FR1.self)) - XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - } - } - } - } - } - } - } + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self).actualView().backUpInWorkflow()) + try await launcher.find(FR4.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertThrowsError(try launcher.find(FR3.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertFalse(binding.wrappedValue, "Binding should be flipped to false") - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } // MARK: PersistWhenSkippedTests diff --git a/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift b/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift index 5a966e494..d20bd15e4 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SkipTests.swift @@ -13,13 +13,9 @@ import ViewInspector import SwiftCurrent @testable import SwiftCurrent_SwiftUI // testable sadly needed for inspection.inspect to work -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class SkipTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testSkippingFirstItemInAWorkflow() throws { + func testSkippingFirstItemInAWorkflow() async throws { // NOTE: Workflows in the past had issues with 4+ items, so this is to cover our bases. SwiftUI also has a nasty habit of behaving a little differently as number of views increase. struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? @@ -38,7 +34,7 @@ final class SkipTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -48,23 +44,15 @@ final class SkipTests: XCTestCase, View { } } } - ).inspection.inspect { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR1.self)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertThrowsError(try launcher.find(FR1.self)) + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - func testSkippingMiddleItemInAWorkflow() throws { + func testSkippingMiddleItemInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -82,7 +70,7 @@ final class SkipTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -92,23 +80,15 @@ final class SkipTests: XCTestCase, View { } } } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR2.self)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR2.self)) + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - func testSkippingLastItemInAWorkflow() throws { + func testSkippingLastItemInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -127,7 +107,7 @@ final class SkipTests: XCTestCase, View { func shouldLoad() -> Bool { false } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -137,23 +117,19 @@ final class SkipTests: XCTestCase, View { } } } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - XCTAssertThrowsError(try fr3.find(FR4.self)) - XCTAssertNoThrow(try fr3.find(FR3.self)) - } - } - } + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR4.self)) + XCTAssertNoThrow(try launcher.find(FR3.self)) - wait(for: [expectViewLoaded, expectOnFinish], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testSkippingMultipleItemsInAWorkflow() throws { + func testSkippingMultipleItemsInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -172,7 +148,7 @@ final class SkipTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -182,23 +158,15 @@ final class SkipTests: XCTestCase, View { } } } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR2.self).actualView()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR3.self).actualView()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(FR2.self).actualView()) + XCTAssertThrowsError(try launcher.find(FR3.self).actualView()) + try await launcher.find(FR4.self).proceedInWorkflow() } - func testSkippingAllItemsInAWorkflow() throws { + func testSkippingAllItemsInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -220,7 +188,7 @@ final class SkipTests: XCTestCase, View { func shouldLoad() -> Bool { false } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -230,20 +198,14 @@ final class SkipTests: XCTestCase, View { } } } - .onFinish { _ in expectOnFinish.fulfill() }) - .inspection.inspect { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR1.self)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR2.self)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR3.self)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertThrowsError(try viewUnderTest.find(FR4.self)) - } - } - } - } + .onFinish { _ in expectOnFinish.fulfill() } + }.hostAndInspect(with: \.inspection) + + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + XCTAssertThrowsError(try launcher.find(FR3.self)) + XCTAssertThrowsError(try launcher.find(FR4.self)) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_ModalTests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_ModalTests.swift index d3db1b4d7..67d2e0c42 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_ModalTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_ModalTests.swift @@ -17,17 +17,13 @@ import SwiftCurrent @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension InspectableView where View == ViewType.Sheet { func isPresented() throws -> Bool { - return (Mirror(reflecting: content.view).descendant("presenter", "isPresented") as? Binding<Bool>)?.wrappedValue ?? false + (Mirror(reflecting: content.view).descendant("presenter", "isPresented") as? Binding<Bool>)?.wrappedValue ?? false } } -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class SwiftCurrent_ModalTests: XCTestCase, Scene { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testWorkflowCanBeFollowed() throws { + func testWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -37,7 +33,7 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { var body: some View { Text("FR2 type") } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self).presentationType(.modal) @@ -45,32 +41,37 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertEqual(try fr1.find(FR1.self).text().string(), "FR1 type") - try fr1.actualView().inspect(model: model, launcher: launcher) { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect(model: model, launcher: launcher) { fr1 in - XCTAssertTrue(try fr1.find(ViewType.Sheet.self).isPresented()) - try fr1.find(ViewType.Sheet.self).find(WorkflowItem<FR2, Never, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssertEqual(try fr2.view(FR2.self).text().string(), "FR2 type") - XCTAssertNoThrow(try fr2.view(FR2.self).actualView().proceedInWorkflow()) - } - } - } } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try wfr1.find(FR1.self).text().string(), "FR1 type") + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) + + let fr2 = try wfr1.find(ViewType.Sheet.self).find(FR2.self) + XCTAssertEqual(try fr2.text().string(), "FR2 type") + try await fr2.proceedInWorkflow() + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testWorkflowItemsOfTheSameTypeCanBeFollowed() throws { + func testWorkflowItemsOfTheSameTypeCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR1.self) { @@ -78,34 +79,32 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { }.presentationType(.modal) } } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { first in - XCTAssert(try first.find(ViewType.Sheet.self).isPresented()) - try first.find(ViewType.Sheet.self).view(WorkflowItem<FR1, WorkflowItem<FR1, Never, FR1>, FR1>.self).actualView().inspect(model: model, launcher: launcher) { second in - XCTAssert(try first.find(ViewType.Sheet.self).isPresented()) - XCTAssertNoThrow(try second.find(FR1.self).actualView().proceedInWorkflow()) - try second.actualView().inspect { second in - XCTAssert(try first.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try second.find(ViewType.Sheet.self).isPresented()) - try second.find(ViewType.Sheet.self).view(WorkflowItem<FR1, Never, FR1>.self).actualView().inspect(model: model, launcher: launcher) { third in - XCTAssertNoThrow(try third.find(FR1.self).actualView().proceedInWorkflow()) - try third.actualView().inspect { third in - XCTAssert(try first.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try second.find(ViewType.Sheet.self).isPresented()) - } - } - } - } - } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + try await wfr2.find(FR1.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr2.find(ViewType.Sheet.self).isPresented()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + try await wfr3.find(FR1.self).proceedInWorkflow() + try await wfr3.actualView().host { $0.environmentObject(model).environmentObject(launcher) } } - func testLargeWorkflowCanBeFollowed() throws { + func testLargeWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -134,16 +133,8 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR7 type") } } - var model: WorkflowViewModel! - var launcher: Launcher! - var fr1: InspectableView<ViewType.View<WorkflowItem<FR1, WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>, FR2>, FR1>>>! - var fr2: InspectableView<ViewType.View<WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>, FR2>>>! - var fr3: InspectableView<ViewType.View<WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>>>! - var fr4: InspectableView<ViewType.View<WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>>>! - var fr5: InspectableView<ViewType.View<WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>>>! - var fr6: InspectableView<ViewType.View<WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>>>! - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -159,100 +150,51 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { }.presentationType(.modal) } } - ).inspection.inspect { fr_1 in - model = (Mirror(reflecting: try fr_1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - launcher = (Mirror(reflecting: try fr_1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertNoThrow(try fr_1.find(FR1.self).actualView().proceedInWorkflow()) - try fr_1.actualView().inspect { fr_1 in - XCTAssert(try fr_1.find(ViewType.Sheet.self).isPresented()) - fr1 = fr_1 - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) - - removeQueuedExpectations() - - try fr1.find(ViewType.Sheet.self).view(WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr_2 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssertNoThrow(try fr_2.find(FR2.self).actualView().proceedInWorkflow()) - try fr_2.actualView().inspect { fr_2 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr_2.find(ViewType.Sheet.self).isPresented()) - fr2 = fr_2 - } + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) } - - removeQueuedExpectations() - - try fr2.find(ViewType.Sheet.self).view(WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr_3 in - XCTAssertNoThrow(try fr_3.find(FR3.self).actualView().proceedInWorkflow()) - try fr_3.actualView().inspect { fr_3 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr_3.find(ViewType.Sheet.self).isPresented()) - fr3 = fr_3 - } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) } - removeQueuedExpectations() + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) - try fr3.find(ViewType.Sheet.self).view(WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>.self).actualView().inspect(model: model, launcher: launcher) { fr_4 in - XCTAssertNoThrow(try fr_4.find(FR4.self).actualView().proceedInWorkflow()) - try fr_4.actualView().inspect { fr_4 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr3.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr_4.find(ViewType.Sheet.self).isPresented()) - fr4 = fr_4 - } - } + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + try await wfr2.find(FR2.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr2.find(ViewType.Sheet.self).isPresented()) - removeQueuedExpectations() - - try fr4.find(ViewType.Sheet.self).view(WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>.self).actualView().inspect(model: model, launcher: launcher) { fr_5 in - XCTAssertNoThrow(try fr_5.find(FR5.self).actualView().proceedInWorkflow()) - try fr_5.actualView().inspect { fr_5 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr3.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr4.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr_5.find(ViewType.Sheet.self).isPresented()) - fr5 = fr_5 - } - } + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + try await wfr3.find(FR3.self).proceedInWorkflow() + try await wfr3.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr3.find(ViewType.Sheet.self).isPresented()) - removeQueuedExpectations() + let wfr4 = try await wfr3.extractWrappedWorkflowItem() + try await wfr4.find(FR4.self).proceedInWorkflow() + try await wfr4.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr4.find(ViewType.Sheet.self).isPresented()) - try fr5.find(ViewType.Sheet.self).view(WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>.self).actualView().inspect(model: model, launcher: launcher) { fr_6 in - XCTAssertNoThrow(try fr_6.find(FR6.self).actualView().proceedInWorkflow()) - try fr_6.actualView().inspect { fr_6 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr3.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr4.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr5.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr_6.find(ViewType.Sheet.self).isPresented()) - fr6 = fr_6 - } - } + let wfr5 = try await wfr4.extractWrappedWorkflowItem() + try await wfr5.find(FR5.self).proceedInWorkflow() + try await wfr5.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr5.find(ViewType.Sheet.self).isPresented()) - removeQueuedExpectations() + let wfr6 = try await wfr5.extractWrappedWorkflowItem() + try await wfr6.find(FR6.self).proceedInWorkflow() + try await wfr6.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr6.find(ViewType.Sheet.self).isPresented()) - try fr6.find(ViewType.Sheet.self).view(WorkflowItem<FR7, Never, FR7>.self).actualView().inspect(model: model, launcher: launcher) { fr7 in - XCTAssertNoThrow(try fr7.find(FR7.self).actualView().proceedInWorkflow()) - try fr7.actualView().inspect { fr7 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr3.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr4.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr5.find(ViewType.Sheet.self).isPresented()) - XCTAssert(try fr6.find(ViewType.Sheet.self).isPresented()) - } - } + let wfr7 = try await wfr6.extractWrappedWorkflowItem() + try await wfr7.find(FR7.self).proceedInWorkflow() } - func testNavLinkWorkflowsCanSkipTheFirstItem() throws { + func testNavLinkWorkflowsCanSkipTheFirstItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -266,7 +208,7 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -274,25 +216,30 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { }.presentationType(.modal) } } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertThrowsError(try fr1.find(FR1.self).actualView()) - try fr1.view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspect { fr2 in - try fr2.find(ViewType.Sheet.self).view(WorkflowItem<FR3, Never, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr3 in - XCTAssert(try fr2.find(ViewType.Sheet.self).isPresented()) - XCTAssertNoThrow(try fr3.find(FR3.self).actualView()) - } - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertThrowsError(try wfr1.find(FR1.self)) + XCTAssertNoThrow(try wfr1.find(FR2.self)) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + try await wfr2.find(FR2.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr2.find(ViewType.Sheet.self).isPresented()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + try await wfr3.find(FR3.self).proceedInWorkflow() } - func testNavLinkWorkflowsCanSkipOneItemInTheMiddle() throws { + func testNavLinkWorkflowsCanSkipOneItemInTheMiddle() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -306,7 +253,8 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -314,24 +262,30 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { }.presentationType(.modal) } } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - try fr1.find(ViewType.Sheet.self).view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).view(WorkflowItem<FR3, Never, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr3 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssertThrowsError(try fr1.find(FR2.self).actualView()) - XCTAssertNoThrow(try fr3.find(FR3.self).actualView()) - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr2.find(FR2.self)) + XCTAssertNoThrow(try wfr2.find(FR3.self)) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + try await wfr3.find(FR3.self).proceedInWorkflow() } - func testNavLinkWorkflowsCanSkipTwoItemsInTheMiddle() throws { + func testNavLinkWorkflowsCanSkipTwoItemsInTheMiddle() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -350,7 +304,8 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -360,25 +315,34 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { }.presentationType(.modal) } } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - try fr1.find(ViewType.Sheet.self).view(WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, Never, FR4>, FR3>, FR2>.self).view(WorkflowItem<FR3, WorkflowItem<FR4, Never, FR4>, FR3>.self).view(WorkflowItem<FR4, Never, FR4>.self).actualView().inspect(model: model, launcher: launcher) { fr4 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssertThrowsError(try fr1.find(FR2.self).actualView()) - XCTAssertThrowsError(try fr1.find(FR3.self).actualView()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView()) - } - } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) } - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr2.find(FR2.self)) + XCTAssertNoThrow(try wfr2.find(FR4.self)) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr3.find(FR3.self)) + XCTAssertNoThrow(try wfr3.find(FR4.self)) + + let wfr4 = try await wfr3.extractWrappedWorkflowItem() + try await wfr4.find(FR4.self).proceedInWorkflow() } - func testNavLinkWorkflowsCanSkipLastItem() throws { + func testNavLinkWorkflowsCanSkipLastItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -394,7 +358,7 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { } let expectOnFinish = expectation(description: "onFinish called") - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -404,20 +368,29 @@ final class SwiftCurrent_ModalTests: XCTestCase, Scene { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - try fr1.find(ViewType.Sheet.self).view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssert(try fr1.find(ViewType.Sheet.self).isPresented()) - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - } - } } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) - } + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + try await wfr1.find(FR1.self).proceedInWorkflow() + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertTrue(try wfr1.find(ViewType.Sheet.self).isPresented()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + try await wfr2.find(FR2.self).proceedInWorkflow() + XCTAssertThrowsError(try wfr2.find(FR3.self)) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr3.find(FR3.self)) + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) + } } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_NavigationLinkTests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_NavigationLinkTests.swift index a410940cf..ea792edfc 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_NavigationLinkTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_NavigationLinkTests.swift @@ -13,13 +13,9 @@ import ViewInspector import SwiftCurrent @testable import SwiftCurrent_SwiftUI // testable sadly needed for inspection.inspect to work -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testWorkflowCanBeFollowed() throws { + func testWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -29,7 +25,7 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { var body: some View { Text("FR2 type") } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) @@ -37,31 +33,39 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertEqual(try fr1.find(FR1.self).text().string(), "FR1 type") - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssertTrue(try fr1.find(ViewType.NavigationLink.self).isActive()) - try fr1.find(ViewType.NavigationLink.self).find(WorkflowItem<FR2, Never, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssertEqual(try fr2.find(FR2.self).text().string(), "FR2 type") - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - } - } } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertEqual(try wfr1.find(FR1.self).text().string(), "FR1 type") + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertTrue(try wfr1.find(ViewType.NavigationLink.self).isActive()) + XCTAssertEqual(try wfr1.find(FR2.self).text().string(), "FR2 type") + try await wfr1.find(FR2.self).proceedInWorkflow() + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testWorkflowItemsOfTheSameTypeCanBeFollowed() throws { + func testWorkflowItemsOfTheSameTypeCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR1.self) { @@ -69,36 +73,34 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { }.presentationType(.navigationLink) }.presentationType(.navigationLink) } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { first in - XCTAssert(try first.find(ViewType.NavigationLink.self).isActive()) - try first.find(ViewType.NavigationLink.self).view(WorkflowItem<FR1, WorkflowItem<FR1, Never, FR1>, FR1>.self).actualView().inspect(model: model, launcher: launcher) { second in - XCTAssert(try first.find(ViewType.NavigationLink.self).isActive()) - XCTAssertFalse(try second.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try second.find(FR1.self).actualView().proceedInWorkflow()) - try second.actualView().inspect { second in - XCTAssert(try first.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try second.find(ViewType.NavigationLink.self).isActive()) - try second.find(ViewType.NavigationLink.self).view(WorkflowItem<FR1, Never, FR1>.self).actualView().inspect(model: model, launcher: launcher) { third in - XCTAssertNoThrow(try third.find(FR1.self).actualView().proceedInWorkflow()) - try third.actualView().inspect { third in - XCTAssert(try first.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try second.find(ViewType.NavigationLink.self).isActive()) - } - } - } - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr1.find(ViewType.NavigationLink.self).isActive()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr2.find(ViewType.NavigationLink.self).isActive()) + try await wfr2.find(FR1.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr2.find(ViewType.NavigationLink.self).isActive()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + try await wfr3.find(FR1.self).proceedInWorkflow() } - func testLargeWorkflowCanBeFollowed() throws { + func testLargeWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -127,7 +129,8 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR7 type") } } - let expectViewLoaded = ViewHosting.loadView( + + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -143,82 +146,58 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { }.presentationType(.navigationLink) }.presentationType(.navigationLink) } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - try fr1.find(ViewType.NavigationLink.self).view(WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertFalse(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspect { fr2 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - try fr2.find(ViewType.NavigationLink.self).view(WorkflowItem<FR3, WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr3 in - XCTAssertFalse(try fr3.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspect { fr3 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr3.find(ViewType.NavigationLink.self).isActive()) - try fr3.find(ViewType.NavigationLink.self).view(WorkflowItem<FR4, WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>, FR4>.self).actualView().inspect(model: model, launcher: launcher) { fr4 in - XCTAssertFalse(try fr4.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - try fr4.actualView().inspect { fr4 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr3.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr4.find(ViewType.NavigationLink.self).isActive()) - try fr4.find(ViewType.NavigationLink.self).view(WorkflowItem<FR5, WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>, FR5>.self).actualView().inspect(model: model, launcher: launcher) { fr5 in - XCTAssertFalse(try fr5.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr5.find(FR5.self).actualView().proceedInWorkflow()) - try fr5.actualView().inspect { fr5 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr3.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr4.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr5.find(ViewType.NavigationLink.self).isActive()) - try fr5.find(ViewType.NavigationLink.self).view(WorkflowItem<FR6, WorkflowItem<FR7, Never, FR7>, FR6>.self).actualView().inspect(model: model, launcher: launcher) { fr6 in - XCTAssertFalse(try fr6.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr6.find(FR6.self).actualView().proceedInWorkflow()) - try fr6.actualView().inspect { fr6 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr3.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr4.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr5.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr6.find(ViewType.NavigationLink.self).isActive()) - try fr6.find(ViewType.NavigationLink.self).view(WorkflowItem<FR7, Never, FR7>.self).actualView().inspect(model: model, launcher: launcher) { fr7 in - XCTAssertNoThrow(try fr7.find(FR7.self).actualView().proceedInWorkflow()) - try fr7.actualView().inspect { fr7 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr3.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr4.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr5.find(ViewType.NavigationLink.self).isActive()) - XCTAssert(try fr6.find(ViewType.NavigationLink.self).isActive()) - } - } - } - } - } - } - } - } - } - } - } - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr1.find(ViewType.NavigationLink.self).isActive()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr2.find(ViewType.NavigationLink.self).isActive()) + try await wfr2.find(FR2.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr2.find(ViewType.NavigationLink.self).isActive()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr3.find(ViewType.NavigationLink.self).isActive()) + try await wfr3.find(FR3.self).proceedInWorkflow() + try await wfr3.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr3.find(ViewType.NavigationLink.self).isActive()) + + let wfr4 = try await wfr3.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr4.find(ViewType.NavigationLink.self).isActive()) + try await wfr4.find(FR4.self).proceedInWorkflow() + try await wfr4.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr4.find(ViewType.NavigationLink.self).isActive()) + + let wfr5 = try await wfr4.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr5.find(ViewType.NavigationLink.self).isActive()) + try await wfr5.find(FR5.self).proceedInWorkflow() + try await wfr5.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr5.find(ViewType.NavigationLink.self).isActive()) + + let wfr6 = try await wfr5.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr6.find(ViewType.NavigationLink.self).isActive()) + try await wfr6.find(FR6.self).proceedInWorkflow() + try await wfr6.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr6.find(ViewType.NavigationLink.self).isActive()) + + let wfr7 = try await wfr6.extractWrappedWorkflowItem() + try await wfr7.find(FR7.self).proceedInWorkflow() } - func testNavLinkWorkflowsCanSkipTheFirstItem() throws { + func testNavLinkWorkflowsCanSkipTheFirstItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -232,7 +211,7 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -240,24 +219,30 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { }.presentationType(.navigationLink) }.presentationType(.navigationLink) } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertThrowsError(try fr1.find(FR1.self).actualView()) - try fr1.view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssertFalse(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.find(ViewType.NavigationLink.self).view(WorkflowItem<FR3, Never, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr3 in - XCTAssert(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr3.find(FR3.self).actualView()) - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertThrowsError(try wfr1.find(FR1.self).actualView()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr2.find(ViewType.NavigationLink.self).isActive()) + try await wfr2.find(FR2.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr2.find(ViewType.NavigationLink.self).isActive()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr3.find(FR3.self).actualView()) } - func testNavLinkWorkflowsCanSkipOneItemInTheMiddle() throws { + func testNavLinkWorkflowsCanSkipOneItemInTheMiddle() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -271,7 +256,7 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -279,25 +264,30 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { }.presentationType(.navigationLink) }.presentationType(.navigationLink) } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - try fr1.find(ViewType.NavigationLink.self).view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).view(WorkflowItem<FR3, Never, FR3>.self).actualView().inspect(model: model, launcher: launcher) { fr3 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertThrowsError(try fr1.find(FR2.self).actualView()) - XCTAssertNoThrow(try fr3.find(FR3.self).actualView()) - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr1.find(ViewType.NavigationLink.self).isActive()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr2.find(FR2.self)) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr3.find(FR3.self).actualView()) } - func testNavLinkWorkflowsCanSkipTwoItemsInTheMiddle() throws { + func testNavLinkWorkflowsCanSkipTwoItemsInTheMiddle() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -316,7 +306,7 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -326,26 +316,33 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { }.presentationType(.navigationLink) }.presentationType(.navigationLink) } - ).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - try fr1.find(ViewType.NavigationLink.self).view(WorkflowItem<FR2, WorkflowItem<FR3, WorkflowItem<FR4, Never, FR4>, FR3>, FR2>.self).view(WorkflowItem<FR3, WorkflowItem<FR4, Never, FR4>, FR3>.self).view(WorkflowItem<FR4, Never, FR4>.self).actualView().inspect(model: model, launcher: launcher) { fr4 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertThrowsError(try fr1.find(FR2.self).actualView()) - XCTAssertThrowsError(try fr1.find(FR3.self).actualView()) - XCTAssertNoThrow(try fr4.find(FR4.self).actualView()) - } - } } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr1.find(ViewType.NavigationLink.self).isActive()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr2.find(FR2.self)) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr3.find(FR3.self).actualView()) + + let wfr4 = try await wfr3.extractWrappedWorkflowItem() + XCTAssertNoThrow(try wfr4.find(FR4.self).actualView()) } - func testNavLinkWorkflowsCanSkipLastItem() throws { + func testNavLinkWorkflowsCanSkipLastItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -361,7 +358,7 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { } let expectOnFinish = expectation(description: "onFinish called") - let expectViewLoaded = ViewHosting.loadView( + let wfr1 = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -371,22 +368,35 @@ final class SwiftCurrent_NavigationLinkTests: XCTestCase, View { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { fr1 in - let model = (Mirror(reflecting: try fr1.actualView()).descendant("_model") as! EnvironmentObject<WorkflowViewModel>).wrappedValue - let launcher = (Mirror(reflecting: try fr1.actualView()).descendant("_launcher") as! EnvironmentObject<Launcher>).wrappedValue - XCTAssertFalse(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - try fr1.find(ViewType.NavigationLink.self).view(WorkflowItem<FR2, WorkflowItem<FR3, Never, FR3>, FR2>.self).actualView().inspect(model: model, launcher: launcher) { fr2 in - XCTAssert(try fr1.find(ViewType.NavigationLink.self).isActive()) - XCTAssertFalse(try fr2.find(ViewType.NavigationLink.self).isActive()) - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - } - } } + } + .hostAndInspect(with: \.inspection) + .extractWorkflowItem() + + let model = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_model") as? EnvironmentObject<WorkflowViewModel>)?.wrappedValue) + } + let launcher = try await MainActor.run { + try XCTUnwrap((Mirror(reflecting: try wfr1.actualView()).descendant("_launcher") as? EnvironmentObject<Launcher>)?.wrappedValue) + } + + XCTAssertFalse(try wfr1.find(ViewType.NavigationLink.self).isActive()) + try await wfr1.find(FR1.self).proceedInWorkflow() + // needed to re-host to avoid some kind of race with the nav link + try await wfr1.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssert(try wfr1.find(ViewType.NavigationLink.self).isActive()) + + let wfr2 = try await wfr1.extractWrappedWorkflowItem() + XCTAssertFalse(try wfr2.find(ViewType.NavigationLink.self).isActive()) + try await wfr2.find(FR2.self).proceedInWorkflow() + try await wfr2.actualView().host { $0.environmentObject(model).environmentObject(launcher) } + XCTAssertFalse(try wfr2.find(ViewType.NavigationLink.self).isActive()) + + let wfr3 = try await wfr2.extractWrappedWorkflowItem() + XCTAssertThrowsError(try wfr3.find(FR3.self)) + XCTAssertNoThrow(try wfr2.find(FR2.self)) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } func testConvenienceEmbedInNavViewFunction() throws { diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index e8f8d57e3..9e2bdd921 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -13,13 +13,9 @@ import ViewInspector import SwiftCurrent @testable import SwiftCurrent_SwiftUI // testable sadly needed for inspection.inspect to work -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - - func testWorkflowCanBeFollowed() throws { + func testWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -29,7 +25,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var body: some View { Text("FR2 type") } } let expectOnFinish = expectation(description: "OnFinish called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) @@ -37,19 +33,19 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - } } + }.hostAndInspect(with: \.inspection) + + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type") + try await launcher.find(FR1.self).proceedInWorkflow() + let fr2 = try launcher.find(FR2.self) + XCTAssertEqual(try fr2.text().string(), "FR2 type") + XCTAssertNoThrow(try fr2.actualView().proceedInWorkflow()) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testWorkflowCanHaveMultipleOnFinishClosures() throws { + func testWorkflowCanHaveMultipleOnFinishClosures() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -60,7 +56,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let expectOnFinish1 = expectation(description: "OnFinish1 called") let expectOnFinish2 = expectation(description: "OnFinish2 called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) } @@ -68,14 +64,15 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { expectOnFinish1.fulfill() }.onFinish { _ in expectOnFinish2.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnFinish1, expectOnFinish2, expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + + wait(for: [expectOnFinish1, expectOnFinish2], timeout: TestConstant.timeout) } - func testWorkflowCanFinishMultipleTimes() throws { + func testWorkflowCanFinishMultipleTimes() async throws { throw XCTSkip("We are currently unable to test this because of a limitation in ViewInspector, see here: https://github.com/nalexn/ViewInspector/issues/126") struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? @@ -85,35 +82,42 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR2 type") } } + @MainActor struct TestUtils { + static var showWorkflow = Binding(wrappedValue: true) + } let expectOnFinish1 = expectation(description: "OnFinish1 called") let expectOnFinish2 = expectation(description: "OnFinish2 called") - var showWorkflow = Binding(wrappedValue: true) - let expectViewLoaded = ViewHosting.loadView( - WorkflowLauncher(isLaunched: showWorkflow) { + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: TestUtils.showWorkflow) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) } } .onFinish { _ in - showWorkflow.wrappedValue = false - showWorkflow.update() - }).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - showWorkflow.wrappedValue = true - showWorkflow.update() - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - showWorkflow.wrappedValue = true - showWorkflow.update() - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - } - - wait(for: [expectOnFinish1, expectOnFinish2, expectViewLoaded], timeout: TestConstant.timeout) + TestUtils.showWorkflow.wrappedValue = false + TestUtils.showWorkflow.update() + } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + await MainActor.run { + TestUtils.showWorkflow.wrappedValue = true + TestUtils.showWorkflow.update() + } + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + await MainActor.run { + TestUtils.showWorkflow.wrappedValue = true + TestUtils.showWorkflow.update() + } + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + + wait(for: [expectOnFinish1, expectOnFinish2], timeout: TestConstant.timeout) } - func testWorkflowPassesArgumentsToTheFirstItem() throws { + func testWorkflowPassesArgumentsToTheFirstItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? let stringProperty: String @@ -123,18 +127,16 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var body: some View { Text("FR1 type") } } let expected = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: expected) { thenProceed(with: FR1.self) - }) - .inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().stringProperty, expected) } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).actualView().stringProperty, expected) } - func testWorkflowPassesArgumentsToTheFirstItem_WhenThatFirstItemTakesInAnyWorkflowPassedArgs() throws { + func testWorkflowPassesArgumentsToTheFirstItem_WhenThatFirstItemTakesInAnyWorkflowPassedArgs() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? let property: AnyWorkflow.PassedArgs @@ -144,19 +146,18 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var body: some View { Text("FR1 type") } } let expected = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: expected) { thenProceed(with: FR1.self) { thenProceed(with: FR1.self) } - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) } - func testWorkflowPassesArgumentsToTheFirstItem_WhenThatFirstItemTakesInAnyWorkflowPassedArgs_AndTheLaunchArgsAreAnyWorkflowPassedArgs() throws { + func testWorkflowPassesArgumentsToTheFirstItem_WhenThatFirstItemTakesInAnyWorkflowPassedArgs_AndTheLaunchArgsAreAnyWorkflowPassedArgs() async throws { struct FR1: View, FlowRepresentable, Inspectable { typealias WorkflowOutput = AnyWorkflow.PassedArgs var _workflowPointer: AnyFlowRepresentable? @@ -167,19 +168,18 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var body: some View { Text("FR1 type") } } let expected = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expected)) { thenProceed(with: FR1.self) { thenProceed(with: FR1.self) } - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).actualView().property.extractArgs(defaultValue: nil) as? String, expected) } - func testWorkflowPassesArgumentsToAllItems() throws { + func testWorkflowPassesArgumentsToAllItems() async throws { struct FR1: View, FlowRepresentable, Inspectable { typealias WorkflowOutput = Int var _workflowPointer: AnyFlowRepresentable? @@ -211,7 +211,8 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { let expectedFR2 = Int.random(in: 1...10) let expectedFR3 = Bool.random() let expectedEnd = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedFR1) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -221,24 +222,18 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } .onFinish { XCTAssertEqual($0.extractArgs(defaultValue: nil) as? String, expectedEnd) - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().property, expectedFR1) - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow(expectedFR2)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR2.self).actualView().property, expectedFR2) - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow(expectedFR3)) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR3.self).actualView().property, expectedFR3) - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow(expectedEnd)) - } - } } + }.hostAndInspect(with: \.inspection) - - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).actualView().property, expectedFR1) + try await launcher.find(FR1.self).proceedInWorkflow(expectedFR2) + XCTAssertEqual(try launcher.find(FR2.self).actualView().property, expectedFR2) + try await launcher.find(FR2.self).proceedInWorkflow(expectedFR3) + XCTAssertEqual(try launcher.find(FR3.self).actualView().property, expectedFR3) + try await launcher.find(FR3.self).proceedInWorkflow(expectedEnd) } - func testLargeWorkflowCanBeFollowed() throws { + func testLargeWorkflowCanBeFollowed() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -267,7 +262,8 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR7 type") } } - let expectViewLoaded = ViewHosting.loadView( + + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -283,32 +279,18 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR4.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR5.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR6.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR7.self).actualView().proceedInWorkflow()) - } - } - } - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() + try await launcher.find(FR5.self).proceedInWorkflow() + try await launcher.find(FR6.self).proceedInWorkflow() + try await launcher.find(FR7.self).proceedInWorkflow() } - func testWorkflowOnlyShowsOneViewAtATime() throws { + func testWorkflowOnlyShowsOneViewAtATime() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -321,7 +303,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR3 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -331,24 +313,16 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } } - ).inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR2.self).actualView().proceedInWorkflow()) - } - } - } - XCTAssertThrowsError(try fr1.find(ViewType.Text.self, skipFound: 1)) - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + XCTAssertThrowsError(try launcher.find(ViewType.Text.self, skipFound: 1)) } - func testMovingBiDirectionallyInAWorkflow() throws { + func testMovingBiDirectionallyInAWorkflow() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -365,7 +339,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR4 type") } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -375,57 +349,42 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } } - ).inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().backUpInWorkflow()) - try fr1.actualView().inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().backUpInWorkflow()) - try fr2.actualView().inspect { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - try fr2.actualView().inspectWrapped { fr3 in - XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow()) - try fr3.actualView().inspectWrapped { fr4 in - XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow()) - } - } - } - } - } - } - } - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertNoThrow(try launcher.find(FR2.self).actualView().backUpInWorkflow()) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() + XCTAssertNoThrow(try launcher.find(FR3.self).actualView().backUpInWorkflow()) + try await launcher.find(FR2.self).proceedInWorkflow() + try await launcher.find(FR3.self).proceedInWorkflow() + try await launcher.find(FR4.self).proceedInWorkflow() } - func testWorkflowSetsBindingBooleanToFalseWhenAbandoned() throws { + func testWorkflowSetsBindingBooleanToFalseWhenAbandoned() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } } let isLaunched = Binding(wrappedValue: true) let expectOnAbandon = expectation(description: "OnAbandon called") - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: isLaunched) { thenProceed(with: FR1.self)} - .onAbandon { - XCTAssertFalse(isLaunched.wrappedValue) - expectOnAbandon.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().workflow?.abandon()) - XCTAssertThrowsError(try viewUnderTest.find(FR1.self)) - } + .onAbandon { + XCTAssertFalse(isLaunched.wrappedValue) + expectOnAbandon.fulfill() + } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnAbandon, expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type") + try await launcher.find(FR1.self).abandonWorkflow() + XCTAssertThrowsError(try launcher.find(FR1.self)) + + wait(for: [expectOnAbandon], timeout: TestConstant.timeout) } - func testWorkflowCanHaveMultipleOnAbandonCallbacks() throws { + func testWorkflowCanHaveMultipleOnAbandonCallbacks() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -433,7 +392,8 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { let isLaunched = Binding(wrappedValue: true) let expectOnAbandon1 = expectation(description: "OnAbandon1 called") let expectOnAbandon2 = expectation(description: "OnAbandon2 called") - let expectViewLoaded = ViewHosting.loadView( + + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: isLaunched) { thenProceed(with: FR1.self) } @@ -443,15 +403,16 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { }.onAbandon { XCTAssertFalse(isLaunched.wrappedValue) expectOnAbandon2.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().workflow?.abandon()) - XCTAssertThrowsError(try viewUnderTest.find(FR1.self)) } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnAbandon1, expectOnAbandon2, expectViewLoaded], timeout: TestConstant.timeout) + try await launcher.find(FR1.self).abandonWorkflow() + XCTAssertThrowsError(try launcher.find(FR1.self)) + + wait(for: [expectOnAbandon1, expectOnAbandon2], timeout: TestConstant.timeout) } - func testWorkflowCanHaveModifiers() throws { + func testWorkflowCanHaveModifiers() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -459,19 +420,17 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { func customModifier() -> Self { self } } - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self).applyModifiers { $0.customModifier().background(Color.blue) - } + thenProceed(with: FR1.self).applyModifiers { $0.customModifier().padding().onAppear { } } } - ).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).background()) - } + }.hostAndInspect(with: \.inspection) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) + XCTAssert(try launcher.find(FR1.self).hasPadding()) + XCTAssertNoThrow(try launcher.find(FR1.self).callOnAppear()) } - func testWorkflowRelaunchesWhenSubsequentlyLaunched() throws { + func testWorkflowRelaunchesWhenSubsequentlyLaunched() async throws { throw XCTSkip("We are currently unable to test this because of a limitation in ViewInspector, see here: https://github.com/nalexn/ViewInspector/issues/126") struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? @@ -484,29 +443,32 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { var body: some View { Text("FR2 type") } } - let binding = Binding(wrappedValue: true) - let workflowView = WorkflowLauncher(isLaunched: binding) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } + @MainActor struct TestUtils { + static let binding = Binding(wrappedValue: true) } - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { viewUnderTest in - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - binding.wrappedValue = false - XCTAssertThrowsError(try viewUnderTest.find(FR1.self)) - XCTAssertThrowsError(try viewUnderTest.find(FR2.self)) + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: TestUtils.binding) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) + } + } + }.hostAndInspect(with: \.inspection) - binding.wrappedValue = true - XCTAssertNoThrow(try viewUnderTest.callOnChange(newValue: false)) - XCTAssertNoThrow(try viewUnderTest.find(FR1.self)) - XCTAssertThrowsError(try viewUnderTest.find(FR2.self)) - } + try await launcher.find(FR1.self).proceedInWorkflow() + + await MainActor.run { TestUtils.binding.wrappedValue = false } + XCTAssertThrowsError(try launcher.find(FR1.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) + + await MainActor.run { TestUtils.binding.wrappedValue = true } + XCTAssertNoThrow(try launcher.callOnChange(newValue: false)) + XCTAssertNoThrow(try launcher.find(FR1.self)) + XCTAssertThrowsError(try launcher.find(FR2.self)) - wait(for: [expectViewLoaded], timeout: TestConstant.timeout) } - func testWorkflowRelaunchesWhenAbandoned_WithAConstantOfTrue() throws { + func testWorkflowRelaunchesWhenAbandoned_WithAConstantOfTrue() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -521,33 +483,27 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let onFinishCalled = expectation(description: "onFinish Called") - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) { - thenProceed(with: FR1.self) { - thenProceed(with: FR2.self) - } - } - .onFinish { _ in - onFinishCalled.fulfill() - } - - let expectViewLoaded = ViewHosting.loadView(workflowView).inspection.inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().abandon()) - XCTAssertThrowsError(try fr2.find(FR2.self)) - try fr1.actualView().inspect { fr1 in - XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow()) - try fr1.actualView().inspectWrapped { fr2 in - XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow()) - } + let launcher = try await MainActor.run { + WorkflowLauncher(isLaunched: .constant(true)) { + thenProceed(with: FR1.self) { + thenProceed(with: FR2.self) } } - } + .onFinish { _ in + onFinishCalled.fulfill() + } + }.hostAndInspect(with: \.inspection) + + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertNoThrow(try launcher.find(FR2.self).actualView().abandon()) + XCTAssertThrowsError(try launcher.find(FR2.self)) + try await launcher.find(FR1.self).proceedInWorkflow() + try await launcher.find(FR2.self).proceedInWorkflow() - wait(for: [expectViewLoaded, onFinishCalled], timeout: TestConstant.timeout) + wait(for: [onFinishCalled], timeout: TestConstant.timeout) } - func testWorkflowCanHaveAPassthroughRepresentable() throws { + func testWorkflowCanHaveAPassthroughRepresentable() async throws { struct FR1: View, FlowRepresentable, Inspectable { typealias WorkflowOutput = AnyWorkflow.PassedArgs var _workflowPointer: AnyFlowRepresentable? @@ -565,7 +521,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: expectedArgs) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) @@ -573,19 +529,18 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow(.args(expectedArgs))) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow()) - } } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type") + try await launcher.find(FR1.self).proceedInWorkflow(.args(expectedArgs)) + XCTAssertEqual(try launcher.find(FR2.self).text().string(), "FR2 type") + try await launcher.find(FR2.self).proceedInWorkflow() + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testWorkflowCanConvertAnyArgsToCorrectTypeForFirstItem() throws { + func testWorkflowCanConvertAnyArgsToCorrectTypeForFirstItem() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? let data: String @@ -603,22 +558,23 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true), startingArgs: AnyWorkflow.PassedArgs.args(expectedArgs)) { thenProceed(with: FR1.self) } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") - XCTAssertEqual(try viewUnderTest.find(FR1.self).actualView().data, expectedArgs) - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) } + }.hostAndInspect(with: \.inspection) + + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type") + XCTAssertEqual(try launcher.find(FR1.self).actualView().data, expectedArgs) + try await launcher.find(FR1.self).proceedInWorkflow() - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } - func testWorkflowCanHaveAPassthroughRepresentableInTheMiddle() throws { + func testWorkflowCanHaveAPassthroughRepresentableInTheMiddle() async throws { struct FR1: View, FlowRepresentable, Inspectable { var _workflowPointer: AnyFlowRepresentable? var body: some View { Text("FR1 type") } @@ -643,7 +599,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } let expectOnFinish = expectation(description: "OnFinish called") let expectedArgs = UUID().uuidString - let expectViewLoaded = ViewHosting.loadView( + let launcher = try await MainActor.run { WorkflowLauncher(isLaunched: .constant(true)) { thenProceed(with: FR1.self) { thenProceed(with: FR2.self) { @@ -653,20 +609,17 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } .onFinish { _ in expectOnFinish.fulfill() - }).inspection.inspect { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR1.self).text().string(), "FR1 type") - XCTAssertNoThrow(try viewUnderTest.find(FR1.self).actualView().proceedInWorkflow()) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR2.self).text().string(), "FR2 type") - XCTAssertNoThrow(try viewUnderTest.find(FR2.self).actualView().proceedInWorkflow(.args(expectedArgs))) - try viewUnderTest.actualView().inspectWrapped { viewUnderTest in - XCTAssertEqual(try viewUnderTest.find(FR3.self).text().string(), "FR3 type, \(expectedArgs)") - XCTAssertNoThrow(try viewUnderTest.find(FR3.self).actualView().proceedInWorkflow()) - } - } } + }.hostAndInspect(with: \.inspection) - wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) + XCTAssertEqual(try launcher.find(FR1.self).text().string(), "FR1 type") + try await launcher.find(FR1.self).proceedInWorkflow() + XCTAssertEqual(try launcher.find(FR2.self).text().string(), "FR2 type") + try await launcher.find(FR2.self).proceedInWorkflow(.args(expectedArgs)) + XCTAssertEqual(try launcher.find(FR3.self).text().string(), "FR3 type, \(expectedArgs)") + try await launcher.find(FR3.self).proceedInWorkflow() + + wait(for: [expectOnFinish], timeout: TestConstant.timeout) } func testWorkflowCorrectlyHandlesState() throws { @@ -698,7 +651,7 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } - func testWorkflowCanHaveADelayedLaunch() throws { + func testWorkflowCanHaveADelayedLaunch() async throws { struct FR1: View, FlowRepresentable, Inspectable { weak var _workflowPointer: AnyFlowRepresentable? @@ -721,15 +674,13 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase, App { } } - let exp = ViewHosting.loadView(Wrapper()).inspection.inspect { view in - let stack = try view.vStack() - let launcher = try stack.view(WorkflowLauncher<WorkflowItem<FR1, Never, FR1>>.self, 1) - XCTAssertThrowsError(try launcher.view(WorkflowItem<FR1, Never, FR1>.self)) - XCTAssertNoThrow(try stack.button(0).tap()) - XCTAssertNoThrow(try launcher.view(WorkflowItem<FR1, Never, FR1>.self)) - } + let wrapper = try await MainActor.run { Wrapper() }.hostAndInspect(with: \.inspection) - wait(for: [exp], timeout: TestConstant.timeout) + let stack = try wrapper.vStack() + let launcher = try stack.view(WorkflowLauncher<WorkflowItem<FR1, Never, FR1>>.self, 1) + XCTAssertThrowsError(try launcher.view(WorkflowItem<FR1, Never, FR1>.self)) + XCTAssertNoThrow(try stack.button(0).tap()) + XCTAssertNoThrow(try launcher.view(WorkflowItem<FR1, Never, FR1>.self)) } } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftUILaunchStyleAdditionTests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftUILaunchStyleAdditionTests.swift index 58869e801..91c3d2c25 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftUILaunchStyleAdditionTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftUILaunchStyleAdditionTests.swift @@ -15,10 +15,6 @@ import SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class LaunchStyleAdditionTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - func testPresentationTypeInitializer() { XCTAssertNil(LaunchStyle.SwiftUI.PresentationType(rawValue: .new)) XCTAssertEqual(LaunchStyle.SwiftUI.PresentationType(rawValue: .default), .default) diff --git a/Tests/SwiftCurrent_SwiftUITests/TestExtensions/XCTestCaseExtensions.swift b/Tests/SwiftCurrent_SwiftUITests/TestExtensions/XCTestCaseExtensions.swift index 18a3db6d5..482a616e2 100644 --- a/Tests/SwiftCurrent_SwiftUITests/TestExtensions/XCTestCaseExtensions.swift +++ b/Tests/SwiftCurrent_SwiftUITests/TestExtensions/XCTestCaseExtensions.swift @@ -9,18 +9,6 @@ import SwiftUI import XCTest -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension XCTestCase { - static var queuedExpectations: [XCTestExpectation] = [] - - func removeQueuedExpectations() { - while let e = Self.queuedExpectations.first { - wait(for: [e], timeout: TestConstant.timeout) - Self.queuedExpectations.removeFirst() - } - } -} - @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension View where Self: XCTestCase { public var body: some View { EmptyView() } diff --git a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/ViewHostingExtensions.swift b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/ViewHostingExtensions.swift index 80f400b53..15f91d8af 100644 --- a/Tests/SwiftCurrent_SwiftUITests/ViewInspector/ViewHostingExtensions.swift +++ b/Tests/SwiftCurrent_SwiftUITests/ViewInspector/ViewHostingExtensions.swift @@ -9,46 +9,102 @@ import Foundation import SwiftUI import ViewInspector import XCTest +import SwiftCurrent @testable import SwiftCurrent_SwiftUI -@available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) -extension ViewHosting { - static func loadView<V: View>(_ view: V) -> V { - defer { - Self.host(view: view) +@available(iOS 15.0, macOS 10.15, tvOS 13.0, *) +extension View where Self: Inspectable { + func host() async { + await MainActor.run { ViewHosting.host(view: self ) } + } + + func host<V: View>(_ transform: (Self) -> V) async { + await MainActor.run { ViewHosting.host(view: transform(self) ) } + } + + func hostAndInspect<E: InspectionEmissary>(with emissary: KeyPath<Self, E>) async throws -> InspectableView<ViewType.View<Self>> where E.V == Self { + await host() + return try await self[keyPath: emissary].inspect() + } +} + +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension InspectableView where View: CustomViewType & SingleViewContent { + func extractWorkflowItem<F, W, C>() async throws -> InspectableView<ViewType.View<WorkflowItem<F, W, C>>> where View.T == WorkflowLauncher<WorkflowItem<F, W, C>> { + let mirror = Mirror(reflecting: try actualView()) + let model = try XCTUnwrap(mirror.descendant("_model") as? StateObject<WorkflowViewModel>) + let launcher = try XCTUnwrap(mirror.descendant("_launcher") as? StateObject<Launcher>) + let actual = try view(WorkflowItem<F, W, C>.self).actualView() + + DispatchQueue.main.async { + ViewHosting.host(view: actual + .environmentObject(model.wrappedValue) + .environmentObject(launcher.wrappedValue)) } - return view + + return try await actual.inspection.inspect() } - static func loadView<F, W, C>(_ view: WorkflowLauncher<WorkflowItem<F, W, C>>) -> WorkflowItem<F, W, C> { - var workflowItem: WorkflowItem<F, W, C>! - let exp = view.inspection.inspect { - do { - workflowItem = try $0.view(WorkflowItem<F, W, C>.self).actualView() - } catch { - XCTFail(error.localizedDescription) - } + func extractWrappedWorkflowItem<F, W, C, PF, PC>() async throws -> InspectableView<ViewType.View<WorkflowItem<F, W, C>>> where View.T == WorkflowItem<PF, WorkflowItem<F, W, C>, PC> { + let wrapped = try await actualView().getWrappedView() + let mirror = Mirror(reflecting: try actualView()) + let model = try XCTUnwrap(mirror.descendant("_model") as? EnvironmentObject<WorkflowViewModel>) + let launcher = try XCTUnwrap(mirror.descendant("_launcher") as? EnvironmentObject<Launcher>) + DispatchQueue.main.async { + ViewHosting.host(view: wrapped + .environmentObject(model.wrappedValue) + .environmentObject(launcher.wrappedValue)) } + return try await wrapped.inspection.inspect() + } +} + +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension InspectableView where View: CustomViewType & SingleViewContent, View.T: FlowRepresentable { + func proceedInWorkflow() async throws where View.T.WorkflowOutput == Never { + try await MainActor.run { try actualView().proceedInWorkflow() } + } - Self.host(view: view) + func proceedInWorkflow(_ args: View.T.WorkflowOutput) async throws where View.T.WorkflowOutput == AnyWorkflow.PassedArgs { + try await MainActor.run { try actualView().proceedInWorkflow(args) } + } - XCTWaiter().wait(for: [exp], timeout: TestConstant.timeout) - XCTAssertNotNil(workflowItem) - let model = Mirror(reflecting: view).descendant("_model") as? StateObject<WorkflowViewModel> - let launcher = Mirror(reflecting: view).descendant("_launcher") as? StateObject<Launcher> - XCTAssertNotNil(model) - XCTAssertNotNil(launcher) - defer { - Self.host(view: workflowItem.environmentObject(model!.wrappedValue).environmentObject(launcher!.wrappedValue)) - } - return workflowItem + func proceedInWorkflow(_ args: View.T.WorkflowOutput) async throws { + try await MainActor.run { try actualView().proceedInWorkflow(args) } + } + + func backUpInWorkflow() async throws { + try await MainActor.run { try actualView().backUpInWorkflow() } + } + + func abandonWorkflow() async throws { + try await MainActor.run { try actualView().workflow?.abandon() } + } +} + +@available(iOS 15.0, macOS 11, tvOS 14.0, watchOS 7.0, *) +extension InspectableView where View: CustomViewType & SingleViewContent, View.T: PassthroughFlowRepresentable { + func proceedInWorkflow() async throws { + try await MainActor.run { try actualView().proceedInWorkflow() } } +} - static func loadView<F, W, C>(_ view: WorkflowItem<F, W, C>, model: WorkflowViewModel, launcher: Launcher) -> WorkflowItem<F, W, C> { - defer { - Self.host(view: view.environmentObject(model).environmentObject(launcher)) +@available(iOS 15.0, macOS 10.15, tvOS 13.0, *) +public extension InspectionEmissary where V: View & Inspectable { + func inspect(after delay: TimeInterval = 0, + function: String = #function, + file: StaticString = #file, + line: UInt = #line) async throws -> InspectableView<ViewType.View<V>> { + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + let exp = self.inspect(after: delay, function: function, file: file, line: line) { view in + continuation.resume(returning: view) + } + DispatchQueue.global(qos: .background).async { + XCTWaiter().wait(for: [exp], timeout: TestConstant.timeout) + } + } } - return view } } diff --git a/Tests/SwiftCurrent_SwiftUITests/WorkflowItemExtensions.swift b/Tests/SwiftCurrent_SwiftUITests/WorkflowItemExtensions.swift index 69e5d4868..e8dc7339b 100644 --- a/Tests/SwiftCurrent_SwiftUITests/WorkflowItemExtensions.swift +++ b/Tests/SwiftCurrent_SwiftUITests/WorkflowItemExtensions.swift @@ -14,23 +14,7 @@ import ViewInspector @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) extension WorkflowItem { - @discardableResult func inspectWrapped<F, W, C>(function: String = #function, file: StaticString = #file, line: UInt = #line, inspection: @escaping (InspectableView<ViewType.View<Wrapped>>) throws -> Void) throws -> XCTestExpectation where Wrapped == WorkflowItem<F, W, C> { - let wrapped = try XCTUnwrap((Mirror(reflecting: self).descendant("_wrapped") as? State<Wrapped?>)?.wrappedValue) - return try wrapped.inspect(function: function, file: file, line: line, inspection: inspection) + func getWrappedView() throws -> Wrapped { + try XCTUnwrap((Mirror(reflecting: self).descendant("_wrapped") as? State<Wrapped?>)?.wrappedValue) } - - @discardableResult func inspect(function: String = #function, file: StaticString = #file, line: UInt = #line, inspection: @escaping (InspectableView<ViewType.View<Self>>) throws -> Void) throws -> XCTestExpectation { - // Waiting for 0.0 seems insane but think about it like "We are waiting for this command to get off the stack" - // Then quit thinking about it, know it was deliberate, and move on. - let expectation = self.inspection.inspect(after: 0, function: function, file: file, line: line) { - try inspection($0) - } - XCTestCase.queuedExpectations.append(expectation) - return expectation - } - - @discardableResult func inspect(model: WorkflowViewModel, launcher: Launcher, function: String = #function, file: StaticString = #file, line: UInt = #line, inspection: @escaping (InspectableView<ViewType.View<Self>>) throws -> Void) throws -> XCTestExpectation { - try ViewHosting.loadView(self, model: model, launcher: launcher).inspect(function: function, file: file, line: line, inspection: inspection) - } - } diff --git a/Tests/SwiftCurrent_SwiftUITests/WorkflowItemTests.swift b/Tests/SwiftCurrent_SwiftUITests/WorkflowItemTests.swift index fbcfab500..c5ac36f4a 100644 --- a/Tests/SwiftCurrent_SwiftUITests/WorkflowItemTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/WorkflowItemTests.swift @@ -14,10 +14,6 @@ import SwiftUI @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class WorkflowItemTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - func testWorkflowItemThrowsFatalError_IfPersistenceCannotBeCast() throws { let item = WorkflowItem(FR.self).persistence { _ in .default diff --git a/Tests/SwiftCurrent_SwiftUITests/WorkflowViewModelTests.swift b/Tests/SwiftCurrent_SwiftUITests/WorkflowViewModelTests.swift index 977889250..1ab1a13e7 100644 --- a/Tests/SwiftCurrent_SwiftUITests/WorkflowViewModelTests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/WorkflowViewModelTests.swift @@ -16,10 +16,6 @@ import SwiftCurrent_Testing @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *) final class WorkflowViewModelTests: XCTestCase, View { - override func tearDownWithError() throws { - removeQueuedExpectations() - } - func testAnyWorkflowElementModelThrowsFatalError_WhenExtractCalledOnSomethingOtherThan_AnyFlowRepresentableView() throws { try XCTAssertThrowsFatalError { _ = AnyWorkflow.Element.createForTests(FR.self).extractErasedView()