Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bd35d4e
[data-drive-json-consumption] - renamed FlowRepresentableMetadataDesc…
Tyler-Keith-Thompson Jan 14, 2022
02b4ce8
[data-drive-json-consumption] - There is now a failing test! We are a…
Tyler-Keith-Thompson Jan 14, 2022
2bfc907
[data-drive-json-consumption] - Hmm, funny how a little infinite recu…
Tyler-Keith-Thompson Jan 14, 2022
89d63b7
[data-drive-json-consumption] - We now reasonably decode AnyWorkflows…
Tyler-Keith-Thompson Jan 14, 2022
635fbce
[data-drive-json-consumption] - Minor refactors and added an addition…
Tyler-Keith-Thompson Jan 14, 2022
857b826
[data-drive-json-consumption] - Code that was temporarily added to su…
Tyler-Keith-Thompson Jan 15, 2022
272c3eb
[data-drive-json-consumption] - After reflection, extending JSON for …
Tyler-Keith-Thompson Jan 15, 2022
2f2d1ef
[data-drive-json-consumption] - Refactor to dedup decoding logic - TT
Tyler-Keith-Thompson Jan 15, 2022
4a234a5
[data-drive-json-consumption] - Removed todo for extending each FR si…
Tyler-Keith-Thompson Jan 18, 2022
7085f54
[data-drive-json-consumption] - Updated WorkflowJSONSpec access level…
Tyler-Keith-Thompson Jan 18, 2022
72a39e4
[data-drive-json-consumption] - Updated docs to satisfy Richard's ped…
Tyler-Keith-Thompson Jan 18, 2022
a8f668e
[data-driven-json-launch-persistence-styles] - Added a failing test f…
Tyler-Keith-Thompson Jan 18, 2022
849d880
[data-driven-json-launch-persistence-styles] - Updated tests to use t…
Tyler-Keith-Thompson Jan 18, 2022
1db2ce0
[data-driven-json-launch-persistence-styles] - SwiftUI now decodes la…
Tyler-Keith-Thompson Jan 18, 2022
a623c18
[data-driven-json-launch-persistence-styles] - Launch styles are now …
Tyler-Keith-Thompson Jan 18, 2022
48538a1
[data-driven-json-launch-persistence-styles] - All the tests pass! ..…
Tyler-Keith-Thompson Jan 18, 2022
1ba2e26
[data-driven-json-launch-persistence-styles] - Updated some missing d…
Tyler-Keith-Thompson Jan 18, 2022
b140c34
[data-driven-json-launch-persistence-styles] - Fixed deprecation warn…
Tyler-Keith-Thompson Jan 18, 2022
73c410a
[data-driven-json-launch-persistence-styles] - Added an additional te…
Tyler-Keith-Thompson Jan 18, 2022
d8427bc
[data-driven-json-launch-persistence-styles] - Fixed cocoapods lib li…
Tyler-Keith-Thompson Jan 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/SwiftCurrentLint/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,6 @@ type_contents_order:
]

file_header:
required_pattern: '// Copyright © 2021 WWT and Tyler Thompson\. All rights reserved\.'
required_pattern: '// Copyright © 202([0-9]) WWT and Tyler Thompson\. All rights reserved\.'

reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, markdown, github-actions-logging)
74 changes: 49 additions & 25 deletions .github/fastlane/README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,88 @@
fastlane documentation
================
----

# Installation

Make sure you have the latest version of the Xcode command line tools installed:

```
```sh
xcode-select --install
```

Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew install fastlane`
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)

# Available Actions

## iOS

### ios unit_test

```sh
[bundle exec] fastlane ios unit_test
```
fastlane ios unit_test
```



### ios build_swiftpm

```sh
[bundle exec] fastlane ios build_swiftpm
```
fastlane ios build_swiftpm
```



### ios cocoapods_liblint

```sh
[bundle exec] fastlane ios cocoapods_liblint
```
fastlane ios cocoapods_liblint
```



### ios lint

```sh
[bundle exec] fastlane ios lint
```
fastlane ios lint
```



### ios lintfix
```
fastlane ios lintfix

```sh
[bundle exec] fastlane ios lintfix
```



### ios patch

```sh
[bundle exec] fastlane ios patch
```
fastlane ios patch
```

Release a new version with a patch bump_type

### ios minor

```sh
[bundle exec] fastlane ios minor
```
fastlane ios minor
```

Release a new version with a minor bump_type

### ios major

```sh
[bundle exec] fastlane ios major
```
fastlane ios major
```

Release a new version with a major bump_type

----

This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).

The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ final class UIKitInteropTests: XCTestCase, View {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class QRScanningViewTests: XCTestCase {
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())
XCTAssertNoThrow(try viewUnderTest.view(CodeScannerView.self).sheet().dismiss())
XCTAssertThrowsError(try viewUnderTest.view(CodeScannerView.self).sheet())
}
wait(for: [exp], timeout: TestConstant.timeout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import Foundation
import UIKit

extension UIApplication {
static func topViewController(of controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
var firstWindow: UIWindow? {
connectedScenes.compactMap { $0 as? UIWindowScene }.first?.windows.first
}

static func topViewController(of controller: UIViewController? = UIApplication.shared.firstWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController,
let visible = navigationController.visibleViewController {
return topViewController(of: visible)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,37 @@ class UIKitConsumerLaunchTests: XCTestCase {
XCTAssertNil((UIApplication.topViewController() as? ExpectedModal)?.navigationController, "You didn't present modally")
}

func testKnownPresentationTypes_CanBeDecoded() throws {
final class TestView: UIViewController, FlowRepresentable, WorkflowDecodable {
weak var _workflowPointer: AnyFlowRepresentable?
}
let validLaunchStyles: [String: LaunchStyle] = [
"automatic": .default,
"navigationStack": .PresentationType.navigationStack.rawValue,
"modal": .PresentationType.modal.rawValue,
"modal(.automatic)": .PresentationType.modal(.automatic).rawValue,
"modal(.currentContext)": .PresentationType.modal(.currentContext).rawValue,
"modal(.custom)": .PresentationType.modal(.custom).rawValue,
"modal(.formSheet)": .PresentationType.modal(.formSheet).rawValue,
"modal(.fullScreen)": .PresentationType.modal(.fullScreen).rawValue,
"modal(.overCurrentContext)": .PresentationType.modal(.overCurrentContext).rawValue,
"modal(.overFullScreen)": .PresentationType.modal(.overFullScreen).rawValue,
"modal(.popover)": .PresentationType.modal(.popover).rawValue,
"modal(.pageSheet)": .PresentationType.modal(.pageSheet).rawValue,
]

let WD: WorkflowDecodable.Type = TestView.self

try validLaunchStyles.forEach { (key, value) in
XCTAssertIdentical(try TestView.decodeLaunchStyle(named: key), value)
XCTAssertIdentical(try WD.decodeLaunchStyle(named: key), value)
}

// Metatest, testing we covered all styles
LaunchStyle.PresentationType.allCases.forEach { presentationType in
XCTAssert(validLaunchStyles.values.contains { $0 === presentationType.rawValue }, "dictionary of validLaunchStyles did not contain one for \(presentationType)")
}
}
}

extension UIKitConsumerLaunchTests {
Expand Down
53 changes: 53 additions & 0 deletions Sources/SwiftCurrent/Extensions/DecoderExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// swiftlint:disable:this file_name
// DecoderExtensions.swift
// SwiftCurrent
//
// Created by Tyler Thompson on 1/14/22.
// Copyright © 2022 WWT and Tyler Thompson. All rights reserved.
//

import Foundation

extension JSONDecoder {
struct WorkflowJSONSpec: Decodable {
let schemaVersion: AnyWorkflow.JSONSchemaVersion
let sequence: [Sequence]

struct Sequence: Decodable {
let flowRepresentableName: String
let launchStyle: String?
let flowPersistence: String?
}
}

/// Convenience method to decode an ``AnyWorkflow`` from Data.
public func decodeWorkflow(withAggregator aggregator: FlowRepresentableAggregator, from data: Data) throws -> AnyWorkflow {
try AnyWorkflow(spec: decode(WorkflowJSONSpec.self, from: data), aggregator: aggregator)
}
}

extension AnyWorkflow {
convenience init(spec: JSONDecoder.WorkflowJSONSpec, aggregator: FlowRepresentableAggregator) throws {
let typeMap = aggregator.typeMap
self.init(Workflow<Never>())
try spec.sequence.forEach {
if let type = typeMap[$0.flowRepresentableName] {
let launchStyle = try getLaunchStyle(decodable: type, from: $0)
let flowPersistence = try getFlowPersistence(decodable: type, from: $0)
append(type.metadataFactory(launchStyle: launchStyle) { _ in flowPersistence })
} else {
throw AnyWorkflow.DecodingError.invalidFlowRepresentable($0.flowRepresentableName)
}
}
}

private func getLaunchStyle(decodable: WorkflowDecodable.Type, from sequence: JSONDecoder.WorkflowJSONSpec.Sequence) throws -> LaunchStyle {
guard let launchStyleName = sequence.launchStyle else { return .default }
return try decodable.decodeLaunchStyle(named: launchStyleName)
}

private func getFlowPersistence(decodable: WorkflowDecodable.Type, from sequence: JSONDecoder.WorkflowJSONSpec.Sequence) throws -> FlowPersistence {
guard let flowPersistenceName = sequence.flowPersistence else { return .default }
return try decodable.decodeFlowPersistence(named: flowPersistenceName)
}
}
24 changes: 24 additions & 0 deletions Sources/SwiftCurrent/PropertyWrappers/DecodeWorkflow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// DecodeWorkflow.swift
// SwiftCurrent
//
// Created by Tyler Thompson on 1/18/22.
// Copyright © 2022 WWT and Tyler Thompson. All rights reserved.
//

import Foundation

@propertyWrapper
public struct DecodeWorkflow<Aggregator: FlowRepresentableAggregator>: Decodable {
public var wrappedValue: AnyWorkflow

public init(wrappedValue: AnyWorkflow = .empty, aggregator: Aggregator.Type) {
self.wrappedValue = wrappedValue
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let spec = try container.decode(JSONDecoder.WorkflowJSONSpec.self)
wrappedValue = try AnyWorkflow(spec: spec, aggregator: Aggregator())
}
}
37 changes: 37 additions & 0 deletions Sources/SwiftCurrent/Protocols/FlowRepresentableAggregator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// FlowRepresentableAggregator.swift
// SwiftCurrent
//
// Created by Tyler Thompson on 1/14/22.
// Copyright © 2022 WWT and Tyler Thompson. All rights reserved.
//

/**
Aggregates ``WorkflowDecodable`` types for decoding.
*/
public protocol FlowRepresentableAggregator {
/// A list of ``WorkflowDecodable`` types to use when decoding a workflow
var types: [WorkflowDecodable.Type] { get }

/**
A dictionary representation of flowRepresentableName to ``WorkflowDecodable``
- NOTE: This is auto-generated unless you override the behavior
*/
var typeMap: [String: WorkflowDecodable.Type] { get }

/**
Creates a FlowRepresentableAggregator with default types.
- NOTE: Convenience methods use this empty initializer; alternative public methods exist for an already initialized aggregator.
*/
init()
}

extension FlowRepresentableAggregator {
/**
A dictionary representation of flowRepresentableName to ``WorkflowDecodable``
- NOTE: This is auto-generated unless you override the behavior
*/
public var typeMap: [String: WorkflowDecodable.Type] {
types.reduce(into: [:]) { $0[$1.flowRepresentableName] = $1 }
}
}

This file was deleted.

52 changes: 52 additions & 0 deletions Sources/SwiftCurrent/Protocols/WorkflowDecodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// FlowRepresentableMetadataDescriber.swift
// SwiftCurrent
//
// Created by Richard Gist on 12/7/21.
// Copyright © 2021 WWT and Tyler Thompson. All rights reserved.
//

/// Aspects of the described ``FlowRepresentable`` needed to dynamically generate metadata from the Workflow Data Scheme.
public protocol WorkflowDecodable {
/// The name of the ``FlowRepresentable`` as used in the Workflow Data Scheme.
static var flowRepresentableName: String { get }

/// Creates a new instance of ``FlowRepresentableMetadata``.
static func metadataFactory(launchStyle: LaunchStyle,
flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) -> FlowRepresentableMetadata

/// Decodes a ``LaunchStyle`` from a string.
static func decodeLaunchStyle(named name: String) throws -> LaunchStyle

/// Decodes a ``FlowPersistence`` from a string.
static func decodeFlowPersistence(named name: String) throws -> FlowPersistence
}

extension WorkflowDecodable {
/// Decodes a ``LaunchStyle`` from a string.
public static func decodeLaunchStyle(named name: String) throws -> LaunchStyle {
throw AnyWorkflow.DecodingError.invalidLaunchStyle(name)
}

/// Decodes a ``FlowPersistence`` from a string.
public static func decodeFlowPersistence(named name: String) throws -> FlowPersistence {
switch name.lowercased() {
case "persistwhenskipped": return .persistWhenSkipped
case "removedafterproceeding": return .removedAfterProceeding
default: throw AnyWorkflow.DecodingError.invalidFlowPersistence(name)
}
}
}

// Provides the implementation for the protocol without immediately conforming FlowRepresentable
// See WorkflowDecodableConsumerTests for reasons.
extension FlowRepresentable where Self: WorkflowDecodable {
/// The name of the ``FlowRepresentable`` as used in the Workflow Data Scheme
public static var flowRepresentableName: String { String(describing: Self.self) }

/// Creates a new instance of ``FlowRepresentableMetadata``
public static func metadataFactory(launchStyle: LaunchStyle,
flowPersistence: @escaping (AnyWorkflow.PassedArgs) -> FlowPersistence) -> FlowRepresentableMetadata {
FlowRepresentableMetadata(Self.self, launchStyle: launchStyle, flowPersistence: flowPersistence)
}
}
Loading