Skip to content

Commit

Permalink
[APP-2869] Add Combine and SwiftUI bridges (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
moglistree committed Feb 15, 2023
1 parent 300e800 commit 65d4f58
Show file tree
Hide file tree
Showing 12 changed files with 702 additions and 30 deletions.
75 changes: 45 additions & 30 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
version: 2.1

anchors:
- &test_device "iPhone Xs"
- &test_device "iPhone 14"
- &test_device_os "16.2"
- &clean_before_build true
- &test_output_folder test_output
- &default_executor
macos:
xcode: "14.0.0"
xcode: "14.2.0"

env:
global:
Expand All @@ -36,11 +36,18 @@ commands:
pod install --verbose
test_main_project:
parameters:
simulator:
type: string
default: *test_device
os_version:
type: string
default: *test_device_os
steps:
- checkout
- test_project_and_store_results:
project: "Flow.xcodeproj"
scheme: "Flow"
simulator: <<parameters.simulator>>
os_version: <<parameters.os_version>>

test_example_project:
parameters:
Expand All @@ -54,30 +61,32 @@ commands:
workspace: "Example.xcworkspace"
scheme: "Example"
path: <<parameters.path>>
test_output_folder: *test_output_folder

# We introduced two separate commands for projects and workspaces because we didn't find a generic and non-confusing way to introduce
# a condition to only pass either the project or the workspace environment argument to the fastlane scan
# a condition to only pass either the project or the workspace environment argument to the test output
test_project_and_store_results:
description: "Builds and tests a project and then stores the results of the tests as artifacts and test results report"
parameters:
project:
simulator:
type: string
scheme:
default: *test_device
os_version:
type: string
default: *test_device_os
steps:
- run:
command: fastlane scan
environment:
SCAN_PROJECT: <<parameters.project>>
SCAN_SCHEME: <<parameters.scheme>>
SCAN_DEVICE: *test_device
SCAN_CLEAN: *clean_before_build
name: Run tests on iOS <<parameters.os_version>>
command: |
xcodebuild -scheme Flow \
-project Flow.xcodeproj \
-destination "platform=iOS Simulator,OS=<<parameters.os_version>>,name=<<parameters.simulator>>" \
build test \
| xcpretty --report junit --output 'test_output/report.junit'
- store_artifacts: # This will by default store an html and junit file as artifacts (See "Artifacts" tab in CircleCI report)
path: *test_output_folder # test_output is the default temporary folder for fastlane scan output
destination: *test_output_folder # This will create a sub structure in the artifacts section in CircleCI
path: test_output # test_output is the default temporary folder for test output
destination: test_output # This will create a sub structure in the artifacts section in CircleCI
- store_test_results: # This will store the test results so you can then see them in the "Test Summary" tab in CircleCI report
path: *test_output_folder
path: test_output

test_workspace_and_store_results:
description: "Builds and tests a workspace and then stores the results of the tests as artifacts and test results report"
Expand All @@ -88,23 +97,27 @@ commands:
type: string
path:
type: string
test_output_folder:
simulator:
type: string
default: *test_device
os_version:
type: string
default: *test_device_os
steps:
- run:
command: |
name: Run examples
command: |
cd <<parameters.path>>
fastlane scan
environment:
SCAN_WORKSPACE: <<parameters.workspace>>
SCAN_SCHEME: <<parameters.scheme>>
SCAN_DEVICE: *test_device
SCAN_CLEAN: *clean_before_build
xcodebuild -workspace <<parameters.workspace>> \
-scheme <<parameters.scheme>> \
-destination "platform=iOS Simulator,OS=<<parameters.os_version>>,name=<<parameters.simulator>>" \
build test \
| xcpretty --report junit --output 'test_output/report.junit'
- store_artifacts: # This will by default store an html and junit file as artifacts (See "Artifacts" tab in CircleCI report)
path: <<parameters.path>>/<<parameters.test_output_folder>> # test_output is the default temporary folder for fastlane scan output
destination: <<parameters.test_output_folder>> # This will create a sub structure in the artifacts section in CircleCI
path: <<parameters.path>>/test_output # test_output is the default temporary folder for test output
destination: test_output # This will create a sub structure in the artifacts section in CircleCI
- store_test_results: # This will store the test results so you can then see them in the "Test Summary" tab in CircleCI report
path: <<parameters.path>>/<<parameters.test_output_folder>>
path: <<parameters.path>>/test_output

jobs:
swiftlint:
Expand Down Expand Up @@ -136,7 +149,9 @@ jobs:
macos:
xcode: "13.0.0"
steps:
- test_main_project
- test_main_project:
simulator: "iPhone 13"
os_version: "15.0"

test-xcode14-ios16:
<<: *default_executor
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 1.11.0
- Add Compose and SwiftUI bridging functions

# 1.10.2
- Specify type of the library for `spm` builds as `dynamic`

Expand Down
114 changes: 114 additions & 0 deletions Disposable+CombineTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// Disposable+CombineTests.swift
// Flow
//
// Created by Carl Ekman on 2023-02-09.
// Copyright © 2023 PayPal Inc. All rights reserved.
//

import XCTest
#if DEBUG
@testable import Flow
#else
import Flow
#endif
import Foundation
#if canImport(Combine)
import Combine

@available(iOS 13.0, macOS 10.15, *)
final class Disposable_CombineTests: XCTestCase {

var bag = CancelBag()

override func tearDownWithError() throws {
bag.cancel()

try super.tearDownWithError()
}

func testCancellingDisposable() {
let disposed = expectation(description: "Disposed")

let disposer = Disposer { disposed.fulfill() }
disposer.asAnyCancellable.cancel()

wait(for: [disposed], timeout: 1)
}

func testCancelBag() {
var bag = CancelBag()

let cancelled1 = expectation(description: "Cancelled 1")
let cancelled2 = expectation(description: "Cancelled 2")
let cancelled3 = expectation(description: "Cancelled 3")

bag += { cancelled1.fulfill() }
bag += { cancelled2.fulfill() }
bag += { cancelled3.fulfill() }

bag.cancel()
XCTAssertFalse(bag.isEmpty)

wait(for: [cancelled1, cancelled2, cancelled3], timeout: 1)
XCTAssertFalse(bag.isEmpty)

bag.empty()
XCTAssertTrue(bag.isEmpty)
}

func testCancellingDisposeBag() {
let bag = DisposeBag()

let cancelled1 = expectation(description: "Cancelled 1")
let cancelled2 = expectation(description: "Cancelled 2")
let cancelled3 = expectation(description: "Cancelled 3")

bag += { cancelled1.fulfill() }
bag += { cancelled2.fulfill() }
bag += { cancelled3.fulfill() }

bag.asAnyCancellable.cancel()

wait(for: [cancelled1, cancelled2, cancelled3], timeout: 1)
}

func testDisposeBagToCancelBag() {
let disposeBag = DisposeBag()

let disposed = expectation(description: "Disposed")

disposeBag += { disposed.fulfill() }

var cancelBag = CancelBag(disposable: disposeBag)
cancelBag.empty()

wait(for: [disposed], timeout: 1)
}

func testCancelPublisherSink() {
let callbacker = Callbacker<Event<Int>>()

let signal = FiniteSignal(callbacker: callbacker)
let publisher = signal.asAnyPublisher

let cancelled = expectation(description: "Cancelled")

bag += { cancelled.fulfill() }

publisher.sink { _ in
XCTFail("Did not expect completion")
} receiveValue: { _ in
XCTFail("Did not expect value")
}.store(in: &bag)

bag.cancel()
callbacker.callAll(with: .value(1))
callbacker.callAll(with: .end(TestError.fatal))

wait(for: [cancelled], timeout: 1)
}

}

#endif
45 changes: 45 additions & 0 deletions Flow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,19 @@
215DEF371DEC368700CEB724 /* RecursiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215DEF351DEC367E00CEB724 /* RecursiveTests.swift */; };
21E1D41C1D9502A300A91CA0 /* Future+Signal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21E1D41B1D9502A300A91CA0 /* Future+Signal.swift */; };
5B46DE3D22E9CC5E00E0A4D9 /* PrefetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B46DE3B22E9CBFA00E0A4D9 /* PrefetchTests.swift */; };
5BB2E8AD2994238C0095F9E1 /* Signal+CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2E8AC2994238C0095F9E1 /* Signal+CombineTests.swift */; };
5BB2E8C32994E5360095F9E1 /* Signal+SwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2E8C02994E5360095F9E1 /* Signal+SwiftUI.swift */; };
5BB2E8C42994E5360095F9E1 /* Future+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2E8C22994E5360095F9E1 /* Future+Combine.swift */; };
5BB2E8C52994E5360095F9E1 /* Signal+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2E8C12994E5360095F9E1 /* Signal+Combine.swift */; };
5BB2E8CC2994F0160095F9E1 /* Future+CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2E8CA2994F0080095F9E1 /* Future+CombineTests.swift */; };
5BE9055B3538F1DDAB424AD2 /* FutureAdditionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE90C011B621BC2F0B9C1B8 /* FutureAdditionsTests.swift */; };
7484FA6B212D9E930076FD3E /* Signal+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7484FA6A212D9E930076FD3E /* Signal+Debug.swift */; };
792AC15B227C8A6800F8BBAD /* SignalProviderTests+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 792AC15A227C8A6800F8BBAD /* SignalProviderTests+Internal.swift */; };
8E890FC106FB7A89BD1727CC /* EitherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E890194B06CBB3311E44757 /* EitherTests.swift */; };
B6B4E3572994FB6500D7FFF2 /* Disposable+Cancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4E3562994FB6500D7FFF2 /* Disposable+Cancellable.swift */; };
B6B4E35A299506BF00D7FFF2 /* Disposable+CombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4E3582995028900D7FFF2 /* Disposable+CombineTests.swift */; };
B6B4E35C29952CCC00D7FFF2 /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4E35B29952CCC00D7FFF2 /* CancelBag.swift */; };
B6B4E36029954B4600D7FFF2 /* Publisher+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B4E35F29954B4600D7FFF2 /* Publisher+Utilities.swift */; };
DA6D58EF230E925700564CC1 /* MemoryUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6D58EB230E918800564CC1 /* MemoryUtilsTests.swift */; };
F610ABAE1D91743500A161AB /* Future+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F610ABA71D91743500A161AB /* Future+Additions.swift */; };
F610ABB01D91743500A161AB /* FutureQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = F610ABA91D91743500A161AB /* FutureQueue.swift */; };
Expand Down Expand Up @@ -86,10 +95,19 @@
215DEF351DEC367E00CEB724 /* RecursiveTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RecursiveTests.swift; path = FlowTests/RecursiveTests.swift; sourceTree = SOURCE_ROOT; };
21E1D41B1D9502A300A91CA0 /* Future+Signal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Future+Signal.swift"; path = "Flow/Future+Signal.swift"; sourceTree = SOURCE_ROOT; };
5B46DE3B22E9CBFA00E0A4D9 /* PrefetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PrefetchTests.swift; path = FlowTests/PrefetchTests.swift; sourceTree = "<group>"; };
5BB2E8AC2994238C0095F9E1 /* Signal+CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Signal+CombineTests.swift"; path = "FlowTests/Signal+CombineTests.swift"; sourceTree = "<group>"; };
5BB2E8C02994E5360095F9E1 /* Signal+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Signal+SwiftUI.swift"; sourceTree = "<group>"; };
5BB2E8C12994E5360095F9E1 /* Signal+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Signal+Combine.swift"; sourceTree = "<group>"; };
5BB2E8C22994E5360095F9E1 /* Future+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Future+Combine.swift"; sourceTree = "<group>"; };
5BB2E8CA2994F0080095F9E1 /* Future+CombineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Future+CombineTests.swift"; path = "FlowTests/Future+CombineTests.swift"; sourceTree = "<group>"; };
5BE90C011B621BC2F0B9C1B8 /* FutureAdditionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FutureAdditionsTests.swift; path = FlowTests/FutureAdditionsTests.swift; sourceTree = "<group>"; };
7484FA6A212D9E930076FD3E /* Signal+Debug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Signal+Debug.swift"; path = "Flow/Signal+Debug.swift"; sourceTree = "<group>"; };
792AC15A227C8A6800F8BBAD /* SignalProviderTests+Internal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "SignalProviderTests+Internal.swift"; path = "FlowTests/SignalProviderTests+Internal.swift"; sourceTree = "<group>"; };
8E890194B06CBB3311E44757 /* EitherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EitherTests.swift; path = FlowTests/EitherTests.swift; sourceTree = "<group>"; };
B6B4E3562994FB6500D7FFF2 /* Disposable+Cancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Disposable+Cancellable.swift"; sourceTree = "<group>"; };
B6B4E3582995028900D7FFF2 /* Disposable+CombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Disposable+CombineTests.swift"; sourceTree = "<group>"; };
B6B4E35B29952CCC00D7FFF2 /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = "<group>"; };
B6B4E35F29954B4600D7FFF2 /* Publisher+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Utilities.swift"; sourceTree = "<group>"; };
DA6D58EB230E918800564CC1 /* MemoryUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MemoryUtilsTests.swift; path = FlowTests/MemoryUtilsTests.swift; sourceTree = "<group>"; };
F610ABA61D91743500A161AB /* Future.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Future.swift; path = Flow/Future.swift; sourceTree = SOURCE_ROOT; };
F610ABA71D91743500A161AB /* Future+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Future+Additions.swift"; path = "Flow/Future+Additions.swift"; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -178,6 +196,20 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
5BB2E8C72994E54C0095F9E1 /* Bridges */ = {
isa = PBXGroup;
children = (
B6B4E35B29952CCC00D7FFF2 /* CancelBag.swift */,
5BB2E8C22994E5360095F9E1 /* Future+Combine.swift */,
5BB2E8C12994E5360095F9E1 /* Signal+Combine.swift */,
5BB2E8C02994E5360095F9E1 /* Signal+SwiftUI.swift */,
B6B4E3562994FB6500D7FFF2 /* Disposable+Cancellable.swift */,
B6B4E35F29954B4600D7FFF2 /* Publisher+Utilities.swift */,
);
name = Bridges;
path = Flow/Bridges;
sourceTree = "<group>";
};
F6442D7220C6C9A400319327 /* Frameworks */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -254,6 +286,7 @@
F688B944205FB78B00BA5A70 /* FlowFramework.podspec */,
F688B943205FB78A00BA5A70 /* Package.swift */,
F6EDC6DE2066BD39007AC39B /* Documentation */,
5BB2E8C72994E54C0095F9E1 /* Bridges */,
F6A8803C1D9181EC00CA257F /* Signal */,
F6D80B5B1BBBB2ED008F8574 /* Future */,
F66F8528205AAE5700347601 /* Scheduler */,
Expand Down Expand Up @@ -315,6 +348,9 @@
5BE90C011B621BC2F0B9C1B8 /* FutureAdditionsTests.swift */,
5B46DE3B22E9CBFA00E0A4D9 /* PrefetchTests.swift */,
DA6D58EB230E918800564CC1 /* MemoryUtilsTests.swift */,
5BB2E8AC2994238C0095F9E1 /* Signal+CombineTests.swift */,
5BB2E8CA2994F0080095F9E1 /* Future+CombineTests.swift */,
B6B4E3582995028900D7FFF2 /* Disposable+CombineTests.swift */,
);
name = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -476,6 +512,7 @@
F6B6A65F2056AEA400B9FC9D /* ReadSignal.swift in Sources */,
F6FF03E71D926AC300B93771 /* Utilities.swift in Sources */,
F66C47A720077B2500333410 /* Signal+Combiners.swift in Sources */,
5BB2E8C42994E5360095F9E1 /* Future+Combine.swift in Sources */,
F662C0A71FDFDEB300E5F869 /* Signal+Scheduling.swift in Sources */,
F6FF03E41D926AC300B93771 /* CoreSignal.swift in Sources */,
F67C4798206CDDCC00BEBDFD /* FiniteSignal.swift in Sources */,
Expand All @@ -486,18 +523,23 @@
F6FF03E21D926AC300B93771 /* Signal+Transforms.swift in Sources */,
F6FF03E31D926AC300B93771 /* Signal+KeyValueObserving.swift in Sources */,
F68EF3551FD58FD20001129C /* UIView+Signal.swift in Sources */,
B6B4E36029954B4600D7FFF2 /* Publisher+Utilities.swift in Sources */,
F6B6A6632056AF4300B9FC9D /* ReadWriteSignal.swift in Sources */,
F68EF3531FD58FC70001129C /* Event.swift in Sources */,
B6B4E35C29952CCC00D7FFF2 /* CancelBag.swift in Sources */,
F6462A421EFAAD06007E2198 /* Scheduler.swift in Sources */,
7484FA6B212D9E930076FD3E /* Signal+Debug.swift in Sources */,
5BB2E8C32994E5360095F9E1 /* Signal+SwiftUI.swift in Sources */,
F6B6A6652056B2CA00B9FC9D /* EventType.swift in Sources */,
B6B4E3572994FB6500D7FFF2 /* Disposable+Cancellable.swift in Sources */,
F610ABB21D91743500A161AB /* Result.swift in Sources */,
F66C47A920077BC700333410 /* Signal+Listeners.swift in Sources */,
F64E975F201888EB00865380 /* Future+Combiners.swift in Sources */,
F6714C711F25E97600C96931 /* Future.swift in Sources */,
F66835CF2091B887002D2676 /* UIView+EditingMenu.swift in Sources */,
F6FF03E51D926AC300B93771 /* TargetActionable.swift in Sources */,
F699C483205C1A5C001378C0 /* Signal+Utilities.swift in Sources */,
5BB2E8C52994E5360095F9E1 /* Signal+Combine.swift in Sources */,
F667FCD8200604570014DA7D /* Enablable.swift in Sources */,
F681B3481DB6566E00E44ABD /* Either.swift in Sources */,
);
Expand All @@ -507,7 +549,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5BB2E8AD2994238C0095F9E1 /* Signal+CombineTests.swift in Sources */,
F610ABBC1D91747000A161AB /* FutureBasicTests.swift in Sources */,
5BB2E8CC2994F0160095F9E1 /* Future+CombineTests.swift in Sources */,
F6A880921D9182B900CA257F /* SignalProviderTests.swift in Sources */,
F610ABBF1D91747000A161AB /* FutureSchedulingTests.swift in Sources */,
F6AC447F1FDE6F240090FBE7 /* SignalConcurrenceyTests.swift in Sources */,
Expand All @@ -523,6 +567,7 @@
792AC15B227C8A6800F8BBAD /* SignalProviderTests+Internal.swift in Sources */,
F6C0FED2202B44360076B877 /* DelegateTests.swift in Sources */,
F6F679A320A966D1004C7AA7 /* EventListenerTests.swift in Sources */,
B6B4E35A299506BF00D7FFF2 /* Disposable+CombineTests.swift in Sources */,
5B46DE3D22E9CC5E00E0A4D9 /* PrefetchTests.swift in Sources */,
F610ABBD1D91747000A161AB /* FutureQueueTests.swift in Sources */,
DA6D58EF230E925700564CC1 /* MemoryUtilsTests.swift in Sources */,
Expand Down
Loading

0 comments on commit 65d4f58

Please sign in to comment.