From f397d7702075c48b767303c7a87428f60b40807a Mon Sep 17 00:00:00 2001 From: Tyler Thompson Date: Mon, 2 Aug 2021 18:16:54 -0600 Subject: [PATCH 1/3] [state-craziness] - Unfortunately no black-box testing worked with view inspector, however we have a test that forces all relevant properties to be state - TT --- .../Views/ModifiedWorkflowView.swift | 41 ++++++++------- .../SwiftCurrent_SwiftUITests.swift | 52 +++++++++++++++++++ 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift index 6ec226bd8..8bb58113b 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift @@ -21,11 +21,12 @@ public struct ModifiedWorkflowView: View { @Binding private var isLaunched: Bool let inspection = Inspection() - private let wrapped: Wrapped? - private let workflow: AnyWorkflow - private let launchArgs: AnyWorkflow.PassedArgs - private var onFinish = [(AnyWorkflow.PassedArgs) -> Void]() - private var onAbandon = [() -> Void]() + // These need to be state variables to survive SwiftUI re-rendering. Change under penalty of death. + @State private var wrapped: Wrapped? + @State private var workflow: AnyWorkflow + @State private var launchArgs: AnyWorkflow.PassedArgs + @State private var onFinish = [(AnyWorkflow.PassedArgs) -> Void]() + @State private var onAbandon = [() -> Void]() @StateObject private var model: WorkflowViewModel @StateObject private var launcher: Launcher @@ -47,13 +48,13 @@ public struct ModifiedWorkflowView: View { } init(_ workflowLauncher: WorkflowLauncher, isLaunched: Binding, item: WorkflowItem) where Wrapped == Never, Args == FR.WorkflowOutput { - wrapped = nil + _wrapped = State(initialValue: nil) let wf = AnyWorkflow(Workflow(item.metadata)) - workflow = wf - launchArgs = workflowLauncher.passedArgs + _workflow = State(initialValue: wf) + _launchArgs = State(initialValue: workflowLauncher.passedArgs) _isLaunched = isLaunched - onFinish = workflowLauncher.onFinish - onAbandon = workflowLauncher.onAbandon + _onFinish = State(initialValue: workflowLauncher.onFinish) + _onAbandon = State(initialValue: workflowLauncher.onAbandon) let model = WorkflowViewModel(isLaunched: isLaunched, launchArgs: workflowLauncher.passedArgs) _model = StateObject(wrappedValue: model) _launcher = StateObject(wrappedValue: Launcher(workflow: wf, @@ -63,22 +64,22 @@ public struct ModifiedWorkflowView: View { private init(_ workflowLauncher: ModifiedWorkflowView, item: WorkflowItem) where Wrapped == ModifiedWorkflowView, Args == FR.WorkflowOutput { _model = workflowLauncher._model - wrapped = workflowLauncher - workflow = workflowLauncher.workflow - workflow.append(item.metadata) - launchArgs = workflowLauncher.launchArgs + _wrapped = State(initialValue: workflowLauncher) + _workflow = workflowLauncher._workflow + _launchArgs = workflowLauncher._launchArgs _isLaunched = workflowLauncher._isLaunched _launcher = workflowLauncher._launcher - onAbandon = workflowLauncher.onAbandon + _onAbandon = workflowLauncher._onAbandon + workflow.append(item.metadata) } private init(workflowLauncher: Self, onFinish: [(AnyWorkflow.PassedArgs) -> Void], onAbandon: [() -> Void]) { _model = workflowLauncher._model - wrapped = workflowLauncher.wrapped - workflow = workflowLauncher.workflow - self.onFinish = onFinish - self.onAbandon = onAbandon - launchArgs = workflowLauncher.launchArgs + _wrapped = workflowLauncher._wrapped + _workflow = workflowLauncher._workflow + _onFinish = State(initialValue: onFinish) + _onAbandon = State(initialValue: onAbandon) + _launchArgs = workflowLauncher._launchArgs _isLaunched = workflowLauncher._isLaunched _launcher = workflowLauncher._launcher } diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index a71764b3a..4852e0b02 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -627,4 +627,56 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { wait(for: [expectOnFinish, expectViewLoaded], timeout: TestConstant.timeout) } + + func testWorkflowCorrectlyHandlesState() throws { + final class Launcher: ObservableObject { + var onFinishCalled = false + init(workflow: AnyWorkflow, + responder: OrchestrationResponder, + launchArgs: AnyWorkflow.PassedArgs) { + if workflow.orchestrationResponder == nil { + workflow.launch(withOrchestrationResponder: responder, passedArgs: launchArgs) + } + } + } + struct ModifiedWorkflowViewExplorer: View, Inspectable { + @Binding var isLaunched: Bool + + let inspection = Inspection() + @State var wrapped: W? + @State var workflow: AnyWorkflow + @State var launchArgs: AnyWorkflow.PassedArgs + @State var onFinish = [(AnyWorkflow.PassedArgs) -> Void]() + @State var onAbandon = [() -> Void]() + + @StateObject private var model: WorkflowViewModel + @StateObject private var launcher: Launcher + + var body: some View { EmptyView() } + } + + struct FR1: View, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + + var body: some View { + Button("Proceed") { proceedInWorkflow() } + } + } + + struct FR2: View, FlowRepresentable { + weak var _workflowPointer: AnyFlowRepresentable? + + var body: some View { + Button("Abandon") { workflow?.abandon() } + } + } + + let workflowView = WorkflowLauncher(isLaunched: .constant(true)) + .thenProceed(with: WorkflowItem(FR1.self)) + + let explorer = unsafeBitCast(workflowView, to: ModifiedWorkflowViewExplorer.self) + + // if the unsafeBitCast worked, then everything is a state var. + XCTAssertNil(explorer.wrapped) + } } From c20a2e342fbdcbd1b5e96199c2eb252a7d51ad81 Mon Sep 17 00:00:00 2001 From: Tyler Thompson Date: Mon, 2 Aug 2021 18:24:27 -0600 Subject: [PATCH 2/3] [state-craziness] - Had one more struct than necessary in the test, fixed - TT --- .../SwiftCurrent_SwiftUITests.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift index 4852e0b02..f90b143f1 100644 --- a/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift +++ b/Tests/SwiftCurrent_SwiftUITests/SwiftCurrent_SwiftUITests.swift @@ -663,14 +663,6 @@ final class SwiftCurrent_SwiftUIConsumerTests: XCTestCase { } } - struct FR2: View, FlowRepresentable { - weak var _workflowPointer: AnyFlowRepresentable? - - var body: some View { - Button("Abandon") { workflow?.abandon() } - } - } - let workflowView = WorkflowLauncher(isLaunched: .constant(true)) .thenProceed(with: WorkflowItem(FR1.self)) From 60cedddfc0bc72e4c384b8ae0e1ff595eb12a9a3 Mon Sep 17 00:00:00 2001 From: Tyler Thompson Date: Tue, 3 Aug 2021 06:32:58 -0600 Subject: [PATCH 3/3] [state-craziness] - Modified the penalty for changing the variables to be worse - TT --- Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift index 8bb58113b..19035f3fe 100644 --- a/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift +++ b/Sources/SwiftCurrent_SwiftUI/Views/ModifiedWorkflowView.swift @@ -21,7 +21,7 @@ public struct ModifiedWorkflowView: View { @Binding private var isLaunched: Bool let inspection = Inspection() - // These need to be state variables to survive SwiftUI re-rendering. Change under penalty of death. + // These need to be state variables to survive SwiftUI re-rendering. Change under penalty of torture BY the codebase you modified. @State private var wrapped: Wrapped? @State private var workflow: AnyWorkflow @State private var launchArgs: AnyWorkflow.PassedArgs