Skip to content

Commit

Permalink
Merge pull request #77 from wwt/die-anyview-die
Browse files Browse the repository at this point in the history
Removes usage of AnyView
  • Loading branch information
brianlombardo authored Jul 22, 2021
2 parents 66b8151 + 1f459b1 commit 0bea914
Show file tree
Hide file tree
Showing 26 changed files with 700 additions and 444 deletions.
4 changes: 2 additions & 2 deletions ExampleApps/SwiftUIExample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ The project is designed to give you an idea of SwiftCurrent functionality while
Here is a list of things that are interesting to look at from a SwiftCurrent perspective.

### ContentView
This starting view shows the creation of the 3 main `WorkflowView`s and how SwiftCurrent can be helpful in a `TabView` setting.
This starting view shows the creation of the 3 main workflows and how SwiftCurrent can be helpful in a `TabView` setting.

### FeatureOnboardingViews
Each feature has an onboarding view. Look at these to see examples of single launch screens that can be placed at the start of a feature flow.

### AccountInformationView
This view launches a `WorkflowView` that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security.
This view launches a workflow that contains a blocking view, `MFAView`, which is an example of SwiftCurrent usage for feature security.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension ProfileFeatureOnboardingView: Inspectable { }
extension QRScannerFeatureOnboardingView: Inspectable { }
extension MapFeatureOnboardingView: Inspectable { }
extension MapFeatureView: Inspectable { }
extension WorkflowView: Inspectable { }
extension ModifiedWorkflowView: Inspectable { }
extension ChangeUsernameView: Inspectable { }
extension ChangePasswordView: Inspectable { }
extension QRScannerFeatureView: Inspectable { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import Swinject
import ViewInspector
import CodeScanner

import SwiftCurrent
@testable import SwiftCurrent_SwiftUI // 🤮 it sucks that this is necessary
@testable import SwiftUIExample

final class AccountInformationViewTests: XCTestCase {
private typealias MFAViewWorkflowView = ModifiedWorkflowView<AnyWorkflow.PassedArgs, Never, MFAView>
private typealias UsernameWorkflow = ModifiedWorkflowView<String, MFAViewWorkflowView, ChangeUsernameView>
private typealias PasswordWorkflow = ModifiedWorkflowView<String, MFAViewWorkflowView, ChangePasswordView>

func testAccountInformationView() throws {
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Username: changeme")
Expand All @@ -26,12 +31,12 @@ final class AccountInformationViewTests: XCTestCase {
}

func testAccountInformationCanLaunchUsernameWorkflow() throws {
var usernameWorkflow: WorkflowView<String>!
var usernameWorkflow: UsernameWorkflow!
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
accountInformation = view
XCTAssertNoThrow(try view.find(ViewType.Button.self, traversal: .depthFirst).tap())
usernameWorkflow = try view.find(WorkflowView<String>.self).actualView()
usernameWorkflow = try view.vStack().view(UsernameWorkflow.self, 0).actualView()
}
wait(for: [exp], timeout: TestConstant.timeout)

Expand All @@ -41,18 +46,23 @@ final class AccountInformationViewTests: XCTestCase {
ViewHosting.loadView(usernameWorkflow)?.inspection.inspect { view in
XCTAssertNoThrow(try view.find(MFAView.self).actualView().proceedInWorkflow(.args("changeme")))
XCTAssertNoThrow(try view.find(ChangeUsernameView.self).actualView().proceedInWorkflow("newName"))
XCTAssertEqual(try accountInformation.find(ViewType.Text.self).string(), "Username: newName")
XCTAssertThrowsError(try accountInformation.find(WorkflowView<String>.self))
}
].compactMap { $0 }, timeout: TestConstant.timeout)

wait(for: [
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
XCTAssertEqual(try view.find(ViewType.Text.self).string(), "Username: newName")
XCTAssertThrowsError(try view.vStack().view(UsernameWorkflow.self, 0))
}
], timeout: TestConstant.timeout)
}

func testAccountInformationDoesNotBlowUp_IfUsernameWorkflowReturnsSomethingWEIRD() throws {
class CustomObj { }
var usernameWorkflow: WorkflowView<String>!
var usernameWorkflow: UsernameWorkflow!
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
XCTAssertNoThrow(try view.find(ViewType.Button.self, traversal: .depthFirst).tap())
usernameWorkflow = try view.find(WorkflowView<String>.self).actualView()
usernameWorkflow = try view.find(UsernameWorkflow.self).actualView()
}
wait(for: [exp], timeout: TestConstant.timeout)

Expand All @@ -67,12 +77,12 @@ final class AccountInformationViewTests: XCTestCase {
}

func testAccountInformationCanLaunchPasswordWorkflow() throws {
var passwordWorkflow: WorkflowView<String>!
var passwordWorkflow: PasswordWorkflow!
var accountInformation: InspectableView<ViewType.View<AccountInformationView>>!
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
accountInformation = view
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
passwordWorkflow = try view.find(WorkflowView<String>.self).actualView()
passwordWorkflow = try view.find(PasswordWorkflow.self).actualView()
}
wait(for: [exp], timeout: TestConstant.timeout)

Expand All @@ -82,18 +92,23 @@ final class AccountInformationViewTests: XCTestCase {
ViewHosting.loadView(passwordWorkflow)?.inspection.inspect { view in
XCTAssertNoThrow(try view.find(MFAView.self).actualView().proceedInWorkflow(.args("changeme")))
XCTAssertNoThrow(try view.find(ChangePasswordView.self).actualView().proceedInWorkflow("newPassword"))
XCTAssertEqual(try accountInformation.actualView().password, "newPassword")
XCTAssertThrowsError(try accountInformation.find(WorkflowView<String>.self))
}
].compactMap { $0 }, timeout: TestConstant.timeout)

wait(for: [
ViewHosting.loadView(try accountInformation.actualView()).inspection.inspect { view in
XCTAssertEqual(try view.actualView().password, "newPassword")
XCTAssertThrowsError(try view.vStack().view(PasswordWorkflow.self, 0))
}
].compactMap { $0 }, timeout: TestConstant.timeout)
}

func testAccountInformationDoesNotBlowUp_IfPasswordWorkflowReturnsSomethingWEIRD() throws {
class CustomObj { }
var passwordWorkflow: WorkflowView<String>!
var passwordWorkflow: PasswordWorkflow!
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
passwordWorkflow = try view.find(WorkflowView<String>.self).actualView()
passwordWorkflow = try view.find(PasswordWorkflow.self).actualView()
}
wait(for: [exp], timeout: TestConstant.timeout)

Expand All @@ -109,14 +124,16 @@ final class AccountInformationViewTests: XCTestCase {

func testAccountInformationCanLaunchBothWorkflows() throws {
let exp = ViewHosting.loadView(AccountInformationView()).inspection.inspect { view in
XCTAssertEqual(view.findAll(WorkflowView<String>.self).count, 0)
XCTAssertThrowsError(try view.find(UsernameWorkflow.self))
XCTAssertThrowsError(try view.find(PasswordWorkflow.self))

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(WorkflowView<String>.self).count, 2)
XCTAssertNoThrow(try view.vStack().view(MFAViewWorkflowView.self, 0))
XCTAssertNoThrow(try view.vStack().view(MFAViewWorkflowView.self, 1))
}
wait(for: [exp], timeout: TestConstant.timeout)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ final class ChangePasswordViewTests: XCTestCase {
func testChangePasswordView() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(1))
XCTAssertNoThrow(try view.form().textField(2))
XCTAssertNoThrow(try view.form().textField(3))
XCTAssertNoThrow(try view.vStack().textField(1))
XCTAssertNoThrow(try view.vStack().textField(2))
XCTAssertNoThrow(try view.vStack().textField(3))
XCTAssertNoThrow(try view.find(ViewType.Button.self))
}
wait(for: [exp], timeout: TestConstant.timeout)
Expand All @@ -27,7 +27,7 @@ final class ChangePasswordViewTests: XCTestCase {
func testChangePasswordProceeds_IfAllInformationIsCorrect() throws {
let currentPassword = UUID().uuidString
let onFinish = expectation(description: "onFinish called")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true), startingArgs: currentPassword)
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true), startingArgs: currentPassword)
.thenProceed(with: WorkflowItem(ChangePasswordView.self))
.onFinish { _ in onFinish.fulfill() }).inspection.inspect { view in
XCTAssertNoThrow(try view.find(ViewType.TextField.self).setInput(currentPassword))
Expand All @@ -41,9 +41,9 @@ final class ChangePasswordViewTests: XCTestCase {
func testErrorsDoNotShowUp_IfFormWasNotSubmitted() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(1).setInput(currentPassword))
XCTAssertNoThrow(try view.form().textField(2).setInput("asdfF1"))
XCTAssertNoThrow(try view.form().textField(3).setInput("asdfF1"))
XCTAssertNoThrow(try view.vStack().textField(1).setInput(currentPassword))
XCTAssertNoThrow(try view.vStack().textField(2).setInput("asdfF1"))
XCTAssertNoThrow(try view.vStack().textField(3).setInput("asdfF1"))
XCTAssertNoThrow(try view.find(ViewType.Button.self))
}
wait(for: [exp], timeout: TestConstant.timeout)
Expand All @@ -52,41 +52,41 @@ final class ChangePasswordViewTests: XCTestCase {
func testIncorrectOldPassword_PrintsError() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(1).setInput("WRONG"))
XCTAssertNoThrow(try view.vStack().textField(1).setInput("WRONG"))
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
XCTAssert(try view.form().text(0).string().contains("Old password does not match records"))
XCTAssert(try view.vStack().text(0).string().contains("Old password does not match records"))
}
wait(for: [exp], timeout: TestConstant.timeout)
}

func testPasswordsNotMatching_PrintsError() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(1).setInput(currentPassword))
XCTAssertNoThrow(try view.form().textField(2).setInput(UUID().uuidString))
XCTAssertNoThrow(try view.form().textField(3).setInput(UUID().uuidString))
XCTAssertNoThrow(try view.vStack().textField(1).setInput(currentPassword))
XCTAssertNoThrow(try view.vStack().textField(2).setInput(UUID().uuidString))
XCTAssertNoThrow(try view.vStack().textField(3).setInput(UUID().uuidString))
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
XCTAssert(try view.form().text(0).string().contains("New password and confirmation password do not match"))
XCTAssert(try view.vStack().text(0).string().contains("New password and confirmation password do not match"))
}
wait(for: [exp], timeout: TestConstant.timeout)
}

func testPasswordsNotHavingUppercase_PrintsError() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(2).setInput("asdf1"))
XCTAssertNoThrow(try view.vStack().textField(2).setInput("asdf1"))
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
XCTAssert(try view.form().text(0).string().contains("Password must contain at least one uppercase character"))
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one uppercase character"))
}
wait(for: [exp], timeout: TestConstant.timeout)
}

func testPasswordsNotHavingNumber_PrintsError() throws {
let currentPassword = UUID().uuidString
let exp = ViewHosting.loadView(ChangePasswordView(with: currentPassword)).inspection.inspect { view in
XCTAssertNoThrow(try view.form().textField(2).setInput("asdfF"))
XCTAssertNoThrow(try view.vStack().textField(2).setInput("asdfF"))
XCTAssertNoThrow(try view.find(ViewType.Button.self).tap())
XCTAssert(try view.form().text(0).string().contains("Password must contain at least one number"))
XCTAssert(try view.vStack().text(0).string().contains("Password must contain at least one number"))
}
wait(for: [exp], timeout: TestConstant.timeout)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,44 @@ import Swinject
@testable import SwiftUIExample

final class ContentViewTests: XCTestCase {
private typealias MapWorkflow = ModifiedWorkflowView<Never, ModifiedWorkflowView<Never, Never, MapFeatureOnboardingView>, MapFeatureView>
private typealias QRScannerWorkflow = ModifiedWorkflowView<Never, ModifiedWorkflowView<Never, Never, QRScannerFeatureOnboardingView>, QRScannerFeatureView>
private typealias ProfileWorkflow = ModifiedWorkflowView<Never, ModifiedWorkflowView<Never, Never, ProfileFeatureOnboardingView>, ProfileFeatureView>

override func setUpWithError() throws {
Container.default.removeAll()
}

func testContentView() throws {
let defaults = try XCTUnwrap(UserDefaults(suiteName: #function))
Container.default.register(UserDefaults.self) { _ in defaults }
var wf1: WorkflowView<Never>!
var wf2: WorkflowView<Never>!
var wf3: WorkflowView<Never>!
var wf1: MapWorkflow!
var wf2: QRScannerWorkflow!
var wf3: ProfileWorkflow!
let exp = ViewHosting.loadView(ContentView()).inspection.inspect { view in
wf1 = try view.tabView().view(WorkflowView<Never>.self, 0).actualView()
XCTAssertEqual(try view.tabView().view(WorkflowView<Never>.self, 0).tabItem().label().title().text().string(), "Map")
wf2 = try view.tabView().view(WorkflowView<Never>.self, 1).actualView()
XCTAssertEqual(try view.tabView().view(WorkflowView<Never>.self, 1).tabItem().label().title().text().string(), "QR Scanner")
wf3 = try view.tabView().view(WorkflowView<Never>.self, 2).actualView()
XCTAssertEqual(try view.tabView().view(WorkflowView<Never>.self, 2).tabItem().label().title().text().string(), "Profile")
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 { workflowView in
XCTAssertNoThrow(try workflowView.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(MapFeatureView.self))
ViewHosting.loadView(wf1)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(MapFeatureView.self))
},
ViewHosting.loadView(wf2)?.inspection.inspect { workflowView in
XCTAssertNoThrow(try workflowView.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(QRScannerFeatureView.self))
ViewHosting.loadView(wf2)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(QRScannerFeatureView.self))
},
ViewHosting.loadView(wf3)?.inspection.inspect { workflowView in
XCTAssertNoThrow(try workflowView.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try workflowView.find(ProfileFeatureView.self))
ViewHosting.loadView(wf3)?.inspection.inspect { WorkflowLauncher in
XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureOnboardingView.self).actualView().proceedInWorkflow())
XCTAssertNoThrow(try WorkflowLauncher.find(ProfileFeatureView.self))
}
].compactMap { $0 }, timeout: TestConstant.timeout)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class MapFeatureOnboardingViewTests: XCTestCase {
defaults.set(false, forKey: defaultsKey)
Container.default.register(UserDefaults.self) { _ in defaults }
let workflowFinished = expectation(description: "View Proceeded")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true))
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(MapFeatureOnboardingView.self))
.onFinish { _ in
workflowFinished.fulfill()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class ProfileFeatureOnboardingViewTests: XCTestCase {
defaults.set(false, forKey: defaultsKey)
Container.default.register(UserDefaults.self) { _ in defaults }
let workflowFinished = expectation(description: "View Proceeded")
let exp = ViewHosting.loadView(WorkflowView(isLaunched: .constant(true))
let exp = ViewHosting.loadView(WorkflowLauncher(isLaunched: .constant(true))
.thenProceed(with: WorkflowItem(ProfileFeatureOnboardingView.self))
.onFinish { _ in
workflowFinished.fulfill()
Expand Down
Loading

0 comments on commit 0bea914

Please sign in to comment.