From 51f7209af350749e2378465716b8baa4f307b65f Mon Sep 17 00:00:00 2001 From: Ben Cochran Date: Wed, 13 Jul 2022 08:14:12 -0700 Subject: [PATCH] Allow RenderTester to recover and continue from missing AnyScreen-producing child workflows --- Package.swift | 7 +++- WorkflowTesting.podspec | 1 + .../Internal/RenderTester+TestContext.swift | 16 +++++++- .../RenderTesterPlaceholderScreen.swift | 36 ++++++++++++++++ .../WorkflowRenderTesterFailureTests.swift | 41 +++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 WorkflowTesting/Sources/Internal/RenderTesterPlaceholderScreen.swift diff --git a/Package.swift b/Package.swift index d71778ff5..c238a72f6 100644 --- a/Package.swift +++ b/Package.swift @@ -99,9 +99,14 @@ let package = Package( ), .target( name: "WorkflowTesting", - dependencies: ["Workflow"], + dependencies: ["Workflow", "WorkflowUI"], path: "WorkflowTesting/Sources" ), + .testTarget( + name: "WorkflowTestingTests", + dependencies: ["Workflow", "WorkflowTesting", "WorkflowUI"], + path: "WorkflowTesting/Tests" + ), .target( name: "WorkflowReactiveSwiftTesting", dependencies: ["WorkflowReactiveSwift", "WorkflowTesting"], diff --git a/WorkflowTesting.podspec b/WorkflowTesting.podspec index fc581e136..543ac1dcf 100644 --- a/WorkflowTesting.podspec +++ b/WorkflowTesting.podspec @@ -19,6 +19,7 @@ Pod::Spec.new do |s| s.source_files = 'WorkflowTesting/Sources/**/*.swift' s.dependency 'Workflow', "#{s.version}" + s.dependency 'WorkflowUI', "#{s.version}" s.framework = 'XCTest' s.test_spec 'Tests' do |test_spec| diff --git a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift index cde14f299..c45518fc7 100644 --- a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift +++ b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift @@ -19,6 +19,10 @@ import XCTest @testable import Workflow + #if canImport(UIKit) && canImport(WorkflowUI) + import WorkflowUI + #endif + extension RenderTester { internal final class TestContext: RenderContextType { var state: WorkflowType.State @@ -68,10 +72,18 @@ // We can “recover” from missing Void-rendering workflows since there’s only one possible value to return if Child.Rendering.self == Void.self { - // Couldn’t find a nicer way to do this polymorphically return () as! Child.Rendering } - fatalError("Unable to continue.") + + #if canImport(UIKit) && canImport(WorkflowUI) + // We can "recover" from missing AnyScreen-rendering workflows since they render an opaque type that we can construct a value of + if Child.Rendering.self == AnyScreen.self { + return RenderTesterPlaceholderScreen().asAnyScreen() as! Child.Rendering + } + #endif + + // At this point, without an return value from an expectation, we have nothing to return from render and are unable to continue + fatalError("Unable to continue; no expectation has given RenderTester a value to return from `render` (and cannot construct an arbitrary value of type \"\(Child.Rendering.self)\").") } let (inserted, _) = usedWorkflowKeys.insert(WorkflowKey(type: ObjectIdentifier(Child.self), key: key)) if !inserted { diff --git a/WorkflowTesting/Sources/Internal/RenderTesterPlaceholderScreen.swift b/WorkflowTesting/Sources/Internal/RenderTesterPlaceholderScreen.swift new file mode 100644 index 000000000..e3a48040b --- /dev/null +++ b/WorkflowTesting/Sources/Internal/RenderTesterPlaceholderScreen.swift @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if DEBUG && canImport(UIKit) && canImport(WorkflowUI) + + import WorkflowUI + import XCTest + + /// Used as the stand-in value returned by RenderTester when an AnyScreen is expected but not provided + struct RenderTesterPlaceholderScreen: Screen { + func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription { + ViewControllerDescription( + type: UIViewController.self, + build: { + XCTFail("Unexpected construction of screen in RenderTester") + return UIViewController() + }, + update: { _ in } + ) + } + } + +#endif diff --git a/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift b/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift index 63f2e2881..c62bc0962 100644 --- a/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift +++ b/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift @@ -16,8 +16,13 @@ import Workflow import WorkflowTesting +import WorkflowUI import XCTest +#if canImport(UIKit) + import UIKit +#endif + /// Who tests the tester? /// /// WorkflowRenderTesterFailureTests does. @@ -132,6 +137,42 @@ final class WorkflowRenderTesterFailureTests: XCTestCase { } } + #if canImport(UIKit) + func test_childWorkflow_unexpected_anyScreenRendering() { + struct MyScreen: Screen { + func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription { + return ViewControllerDescription( + type: UIViewController.self, + build: { UIViewController() }, + update: { _ in } + ) + } + } + + struct MyChildWorkflow: Workflow { + typealias State = Void + func render(state: State, context: RenderContext) -> AnyScreen { + return MyScreen().asAnyScreen() + } + } + + struct MyWorkflow: Workflow { + typealias State = Void + func render(state: State, context: RenderContext) -> AnyScreen { + let childRendering = MyChildWorkflow().rendered(in: context) + return childRendering + } + } + + let tester = MyWorkflow() + .renderTester() + + expectingFailure(#"Unexpected workflow of type MyChildWorkflow with key """#) { + tester.render { _ in } + } + } + #endif + func test_childWorkflowMultipleRenders_sameKey() { let tester = ParentWorkflow() .renderTester()