Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26faf90356 | |||
| 511d676c30 | |||
| f6ecc28d25 | |||
| 03fe398395 | |||
| 47bbd8d81a | |||
| 5e3a18d8c7 | |||
| 74e1c1ae32 | |||
| 4dbc8cc09b | |||
| 2849b1c195 | |||
| c3e6905c68 | |||
| e75ad6b0de | |||
| c790e5f708 | |||
| 065b981934 | |||
| fbd1fd7014 |
@@ -72,12 +72,6 @@ line_length:
|
||||
generic_type_name:
|
||||
min_length: 3
|
||||
|
||||
file_types_order:
|
||||
order:
|
||||
- main_type
|
||||
- extension
|
||||
- supporting_type
|
||||
|
||||
attributes:
|
||||
always_on_line_above:
|
||||
- "@usableFromInline"
|
||||
|
||||
+11
-14
@@ -12,19 +12,15 @@ matrix:
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
|
||||
env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | Tests"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES"
|
||||
# - name: "macOS 10.14 | Swift 5.1"
|
||||
# os: osx
|
||||
# osx_image: xcode10.2
|
||||
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a"
|
||||
# - name: "macOS 10.15 | Swift 5.1"
|
||||
# os: osx
|
||||
# osx_image: xcode11.0
|
||||
# env: SWIFT_VERSION="swift-5.1-DEVELOPMENT-SNAPSHOT-2019-06-16-a" OPENCOMBINE_COMPATIBILITY_TEST="YES"
|
||||
env: SWIFT_VERSION="5.0" CODE_COVERAGE="YES" OPENCOMBINE_TEST="YES"
|
||||
# - name: "iOS 13.0 | Swift 5.1 | Compatibility Tests"
|
||||
# os: osx
|
||||
# osx_image: xcode11
|
||||
# env: SWIFT_VERSION="5.1" OPENCOMBINE_COMPATIBILITY_TEST="YES"
|
||||
- name: "macOS 10.14 | Swift 5.0 | SwiftLint"
|
||||
os: osx
|
||||
osx_image: xcode10.2
|
||||
@@ -41,14 +37,15 @@ install:
|
||||
gem install xcpretty;
|
||||
fi
|
||||
script:
|
||||
- if [[ $SWIFT_LINT != "YES" ]]; then
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
swift test -c debug --enable-code-coverage --sanitize thread;
|
||||
fi
|
||||
- if [[ $SWIFT_LINT != "YES" ]]; then
|
||||
- if [[ $OPENCOMBINE_TEST == "YES" ]]; then
|
||||
swift test -c release;
|
||||
fi
|
||||
- if [[ $OPENCOMBINE_COMPATIBILITY_TEST == "YES" ]]; then
|
||||
swift test -c release -Xswiftc -DOPENCOMBINE_COMPATIBILITY_TEST;
|
||||
swift package generate-xcodeproj --xcconfig-overrides iOS-Combine-Compatibility.xcconfig;
|
||||
set -o pipefail && xcodebuild -scheme OpenCombine-Package -sdk iphonesimulator13.0 -destination "platform=iOS Simulator,name=iPhone Xs,OS=13.0" build test | xcpretty;
|
||||
fi
|
||||
- if [[ $SWIFT_LINT == "YES" ]]; then
|
||||
swiftlint lint --strict --reporter "emoji";
|
||||
@@ -57,5 +54,5 @@ after_success:
|
||||
- if [[ $CODE_COVERAGE == "YES" ]]; then
|
||||
swift package generate-xcodeproj --enable-code-coverage;
|
||||
xcodebuild -scheme OpenCombine-Package build test | xcpretty;
|
||||
bash <(curl -s https://codecov.io/bash) -t $CODECOV_TOKEN;
|
||||
bash <(curl -s https://codecov.io/bash);
|
||||
fi
|
||||
|
||||
+306
-198
@@ -260,39 +260,6 @@ extension Publisher {
|
||||
public func breakpointOnError() -> Publishers.Breakpoint<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that awaits subscription before running the supplied closure to create a publisher for the new subscriber.
|
||||
public struct Deferred<DeferredPublisher> : Publisher where DeferredPublisher : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = DeferredPublisher.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = DeferredPublisher.Failure
|
||||
|
||||
/// The closure to execute when it receives a subscription.
|
||||
///
|
||||
/// The publisher returned by this closure immediately receives the incoming subscription.
|
||||
public let createPublisher: () -> DeferredPublisher
|
||||
|
||||
/// Creates a deferred publisher.
|
||||
///
|
||||
/// - Parameter createPublisher: The closure to execute when calling `subscribe(_:)`.
|
||||
public init(createPublisher: @escaping () -> DeferredPublisher)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, DeferredPublisher.Failure == S.Failure, DeferredPublisher.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes a single Boolean value that indicates whether all received elements pass a given predicate.
|
||||
@@ -760,6 +727,7 @@ extension Publisher {
|
||||
public func tryPrefix(while predicate: @escaping (Self.Output) throws -> Bool) -> Publishers.TryPrefixWhile<Self>
|
||||
}
|
||||
|
||||
/// A publisher that eventually produces one value and then finishes or fails.
|
||||
final public class Future<Output, Failure> : Publisher where Failure : Error {
|
||||
|
||||
public typealias Promise = (Result<Output, Failure>) -> Void
|
||||
@@ -775,48 +743,6 @@ final public class Future<Output, Failure> : Publisher where Failure : Error {
|
||||
final public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
|
||||
}
|
||||
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// The publisher cannot actually fail with the specified type and instead just finishes normally. Use this publisher type when you need to match the error types for two mismatched publishers.
|
||||
public struct SetFailureType<Upstream, Failure> : Publisher where Upstream : Publisher, Failure : Error, Upstream.Failure == Never {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Creates a publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives elements.
|
||||
public init(upstream: Upstream)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where Failure == S.Failure, S : Subscriber, Upstream.Output == S.Input
|
||||
|
||||
public func setFailureType<E>(to failure: E.Type) -> Publishers.SetFailureType<Upstream, E> where E : Error
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Self.Failure == Never {
|
||||
|
||||
/// Changes the failure type declared by the upstream publisher.
|
||||
///
|
||||
/// The publisher returned by this method cannot actually fail with the specified type and instead just finishes normally. Instead, you use this method when you need to match the error types of two mismatched publishers.
|
||||
///
|
||||
/// - Parameter failureType: The `Failure` type presented by this publisher.
|
||||
/// - Returns: A publisher that appears to send the specified failure type.
|
||||
public func setFailureType<E>(to failureType: E.Type) -> Publishers.SetFailureType<Self, E> where E : Error
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that emits a Boolean value upon receiving an element that satisfies the predicate closure.
|
||||
@@ -1157,11 +1083,6 @@ extension Publisher {
|
||||
public func prefix<P>(untilOutputFrom publisher: P) -> Publishers.PrefixUntilOutput<Self, P> where P : Publisher
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
public func subscribe<S>(_ subject: S) -> AnyCancellable where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that applies a closure to all received elements and produces an accumulated value when the upstream publisher finishes.
|
||||
@@ -1760,7 +1681,7 @@ extension Publisher {
|
||||
/// Transforms elements from the upstream publisher by providing the current element to a closure along with the last value returned by the closure.
|
||||
///
|
||||
/// let pub = (0...5)
|
||||
/// .publisher()
|
||||
/// .publisher
|
||||
/// .scan(0, { return $0 + $1 })
|
||||
/// .sink(receiveValue: { print ("\($0)", terminator: " ") })
|
||||
/// // Prints "0 1 3 6 10 15 ".
|
||||
@@ -1782,43 +1703,6 @@ extension Publisher {
|
||||
public func tryScan<T>(_ initialResult: T, _ nextPartialResult: @escaping (T, Self.Output) throws -> T) -> Publishers.TryScan<Self, T>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the number of elements received from the upstream publisher.
|
||||
public struct Count<Upstream> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Int
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == Publishers.Count<Upstream>.Output
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher finishes, then emits a single
|
||||
/// value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that only publishes the last element of a stream that satisfies a predicate closure, once the stream finishes.
|
||||
@@ -2031,45 +1915,6 @@ extension Publisher {
|
||||
public func retry(_ retries: Int) -> Publishers.Retry<Self>
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that converts any failure from the upstream publisher into a new error.
|
||||
public struct MapError<Upstream, Failure> : Publisher where Upstream : Publisher, Failure : Error {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that converts the upstream failure into a new error.
|
||||
public let transform: (Upstream.Failure) -> Failure
|
||||
|
||||
public init(upstream: Upstream, transform: @escaping (Upstream.Failure) -> Failure)
|
||||
|
||||
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where Failure == S.Failure, S : Subscriber, Upstream.Output == S.Input
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Converts any failure from the upstream publisher into a new error.
|
||||
///
|
||||
/// Until the upstream publisher finishes normally or fails with an error, the returned publisher republishes all the elements it receives.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes the upstream failure as a parameter and returns a new error for the publisher to terminate with.
|
||||
/// - Returns: A publisher that replaces any upstream failure with a new error produced by the `transform` closure.
|
||||
public func mapError<E>(_ transform: @escaping (Self.Failure) -> E) -> Publishers.MapError<Self, E> where E : Error
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes either the most-recent or first element published by the upstream publisher in a specified time interval.
|
||||
@@ -2332,15 +2177,6 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces nil elements in the stream with the proviced element.
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from the upstream publisher with the provided element.
|
||||
public func replaceNil<T>(with output: T) -> Publishers.Map<Self, T> where Self.Output == T?
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces any errors in the stream with the provided element.
|
||||
@@ -2794,6 +2630,12 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.PrefetchStrategy : Equatable {
|
||||
}
|
||||
|
||||
extension Publishers.PrefetchStrategy : Hashable {
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
public func buffer(size: Int, prefetch: Publishers.PrefetchStrategy, whenFull: Publishers.BufferingStrategy<Self.Failure>) -> Publishers.Buffer<Self>
|
||||
@@ -3086,7 +2928,7 @@ extension Publisher {
|
||||
/// The following example replaces any error from the upstream publisher and replaces the upstream with a `Just` publisher. This continues the stream by publishing a single value and completing normally.
|
||||
/// ```
|
||||
/// enum SimpleError: Error { case error }
|
||||
/// let errorPublisher = (0..<10).publisher().tryMap { v -> Int in
|
||||
/// let errorPublisher = (0..<10).publisher.tryMap { v -> Int in
|
||||
/// if v < 5 {
|
||||
/// return v
|
||||
/// } else {
|
||||
@@ -3180,7 +3022,9 @@ extension Publishers {
|
||||
/// The scheduler to deliver the delayed events.
|
||||
public let scheduler: Context
|
||||
|
||||
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, tolerance: Context.SchedulerTimeType.Stride, scheduler: Context)
|
||||
public let options: Context.SchedulerOptions?
|
||||
|
||||
public init(upstream: Upstream, interval: Context.SchedulerTimeType.Stride, tolerance: Context.SchedulerTimeType.Stride, scheduler: Context, options: Context.SchedulerOptions? = nil)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
@@ -3192,16 +3036,17 @@ extension Publishers {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount of time on a particular scheduler.
|
||||
///
|
||||
/// The delay affects the delivery of elements and completion, but not of the original subscription.
|
||||
/// - Parameters:
|
||||
/// - interval: The amount of time to delay.
|
||||
/// - tolerance: The allowed tolerance in firing delayed events.
|
||||
/// - scheduler: The scheduler to deliver the delayed events.
|
||||
/// - options: Options relevant to the scheduler’s behavior.
|
||||
/// - Returns: A publisher that delays delivery of elements and completion to the downstream receiver.
|
||||
public func delay<S>(for interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Delay<Self, S> where S : Scheduler
|
||||
extension Publisher {
|
||||
|
||||
/// Delays delivery of all output to the downstream receiver by a specified amount of time on a particular scheduler.
|
||||
///
|
||||
/// The delay affects the delivery of elements and completion, but not of the original subscription.
|
||||
/// - Parameters:
|
||||
/// - interval: The amount of time to delay.
|
||||
/// - tolerance: The allowed tolerance in firing delayed events.
|
||||
/// - scheduler: The scheduler to deliver the delayed events.
|
||||
/// - Returns: A publisher that delays delivery of elements and completion to the downstream receiver.
|
||||
public func delay<S>(for interval: S.SchedulerTimeType.Stride, tolerance: S.SchedulerTimeType.Stride? = nil, scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.Delay<Self, S> where S : Scheduler
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
@@ -3424,19 +3269,6 @@ extension Publishers.CombineLatest4 : Equatable where A : Equatable, B : Equatab
|
||||
public static func == (lhs: Publishers.CombineLatest4<A, B, C, D>, rhs: Publishers.CombineLatest4<A, B, C, D>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
///
|
||||
/// Equality is the inverse of inequality. For any values `a` and `b`,
|
||||
/// `a == b` implies that `a != b` is `false`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - lhs: A value to compare.
|
||||
/// - rhs: Another value to compare.
|
||||
public static func == (lhs: Publishers.SetFailureType<Upstream, Failure>, rhs: Publishers.SetFailureType<Upstream, Failure>) -> Bool
|
||||
}
|
||||
|
||||
extension Publishers.Collect : Equatable where Upstream : Equatable {
|
||||
|
||||
/// Returns a Boolean value indicating whether two values are equal.
|
||||
@@ -3746,22 +3578,22 @@ extension Publishers.First : Equatable where Upstream : Equatable {
|
||||
/// Adds a `Publisher` to a property.
|
||||
///
|
||||
/// Properties annotated with `@Published` contain both the stored value and a publisher which sends any new values after the property value has been sent. New subscribers will receive the current value of the property first.
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
@propertyWrapper public struct Published<Value> {
|
||||
|
||||
/// Initialize the storage of the Published property as well as the corresponding `Publisher`.
|
||||
public init(initialValue: Value)
|
||||
|
||||
/// The current value of the property.
|
||||
public var value: Value
|
||||
public static subscript<EnclosingSelf: AnyObject>(
|
||||
_enclosingInstance object: EnclosingSelf,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>
|
||||
) -> Value { get set }
|
||||
|
||||
public class Publisher : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Value
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
@@ -3773,5 +3605,281 @@ extension Publishers.First : Equatable where Upstream : Equatable {
|
||||
public func receive<S>(subscriber: S) where Value == S.Input, S : Subscriber, S.Failure == Published<Value>.Publisher.Failure
|
||||
}
|
||||
|
||||
public var delegateValue: Published<Value>.Publisher { mutating get }
|
||||
/// The property that can be accessed with the `$` syntax and allows access to the `Publisher`
|
||||
public var projectedValue: Published<Value>.Publisher { mutating get }
|
||||
}
|
||||
|
||||
/// A type of object with a publisher that emits before the object has changed.
|
||||
///
|
||||
/// By default an `ObservableObject` will synthesize an `objectWillChange`
|
||||
/// publisher that emits before any of its `@Published` properties changes:
|
||||
///
|
||||
/// class Contact : ObservableObject {
|
||||
/// @Published var name: String
|
||||
/// @Published var age: Int
|
||||
///
|
||||
/// init(name: String, age: Int) {
|
||||
/// self.name = name
|
||||
/// self.age = age
|
||||
/// }
|
||||
///
|
||||
/// func haveBirthday() -> Int {
|
||||
/// age += 1
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let john = Contact(name: "John Appleseed", age: 24)
|
||||
/// john.objectWillChange.sink { _ in print("will change") }
|
||||
/// print(john.haveBirthday)
|
||||
/// // Prints "will change"
|
||||
/// // Prints "25"
|
||||
///
|
||||
public protocol ObservableObject : AnyObject {
|
||||
|
||||
/// The type of publisher that emits before the object has changed.
|
||||
associatedtype ObjectWillChangePublisher : Publisher = ObservableObjectPublisher where Self.ObjectWillChangePublisher.Failure == Never
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
var objectWillChange: Self.ObjectWillChangePublisher { get }
|
||||
}
|
||||
|
||||
extension ObservableObject where Self.ObjectWillChangePublisher == ObservableObjectPublisher {
|
||||
|
||||
/// A publisher that emits before the object has changed.
|
||||
public var objectWillChange: ObservableObjectPublisher { get }
|
||||
}
|
||||
|
||||
/// The default publisher of an `ObservableObject`.
|
||||
final public class ObservableObjectPublisher : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Void
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Never
|
||||
|
||||
public init()
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output
|
||||
|
||||
final public func send()
|
||||
}
|
||||
|
||||
/// A publisher that allows for recording a series of inputs and a completion for later playback to each subscriber.
|
||||
public struct Record<Output, Failure> : Publisher where Failure : Error {
|
||||
|
||||
/// The recorded output and completion.
|
||||
public let recording: Record<Output, Failure>.Recording
|
||||
|
||||
/// Interactively record a series of outputs and a completion.
|
||||
public init(record: (inout Record<Output, Failure>.Recording) -> Void)
|
||||
|
||||
/// Initialize with a recording.
|
||||
public init(recording: Record<Output, Failure>.Recording)
|
||||
|
||||
/// Set up a complete recording with the specified output and completion.
|
||||
public init(output: [Output], completion: Subscribers.Completion<Failure>)
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
|
||||
|
||||
/// A recorded set of `Output` and a `Subscribers.Completion`.
|
||||
public struct Recording {
|
||||
|
||||
public typealias Input = Output
|
||||
|
||||
/// The output which will be sent to a `Subscriber`.
|
||||
public var output: [Output] { get }
|
||||
|
||||
/// The completion which will be sent to a `Subscriber`.
|
||||
public var completion: Subscribers.Completion<Failure> { get }
|
||||
|
||||
/// Set up a recording in a state ready to receive output.
|
||||
public init()
|
||||
|
||||
/// Set up a complete recording with the specified output and completion.
|
||||
public init(output: [Output], completion: Subscribers.Completion<Failure> = .finished)
|
||||
|
||||
/// Add an output to the recording.
|
||||
///
|
||||
/// A `fatalError` will be raised if output is added after adding completion.
|
||||
public mutating func receive(_ input: Record<Output, Failure>.Recording.Input)
|
||||
|
||||
/// Add a completion to the recording.
|
||||
///
|
||||
/// A `fatalError` will be raised if more than one completion is added.
|
||||
public mutating func receive(completion: Subscribers.Completion<Failure>)
|
||||
}
|
||||
}
|
||||
|
||||
extension Record : Codable where Output : Decodable, Output : Encodable, Failure : Decodable, Failure : Encodable {
|
||||
|
||||
/// Creates a new instance by decoding from the given decoder.
|
||||
///
|
||||
/// This initializer throws an error if reading from the decoder fails, or
|
||||
/// if the data read is corrupted or otherwise invalid.
|
||||
///
|
||||
/// - Parameter decoder: The decoder to read data from.
|
||||
public init(from decoder: Decoder) throws
|
||||
|
||||
/// Encodes this value into the given encoder.
|
||||
///
|
||||
/// If the value fails to encode anything, `encoder` will encode an empty
|
||||
/// keyed container in its place.
|
||||
///
|
||||
/// This function throws an error if any values are invalid for the given
|
||||
/// encoder's format.
|
||||
///
|
||||
/// - Parameter encoder: The encoder to write data to.
|
||||
public func encode(to encoder: Encoder) throws
|
||||
}
|
||||
|
||||
extension Record.Recording : Codable where Output : Decodable, Output : Encodable, Failure : Decodable, Failure : Encodable {
|
||||
|
||||
/// Creates a new instance by decoding from the given decoder.
|
||||
///
|
||||
/// This initializer throws an error if reading from the decoder fails, or
|
||||
/// if the data read is corrupted or otherwise invalid.
|
||||
///
|
||||
/// - Parameter decoder: The decoder to read data from.
|
||||
public init(from decoder: Decoder) throws
|
||||
|
||||
public func encode(into encoder: Encoder) throws
|
||||
|
||||
/// Encodes this value into the given encoder.
|
||||
///
|
||||
/// If the value fails to encode anything, `encoder` will encode an empty
|
||||
/// keyed container in its place.
|
||||
///
|
||||
/// This function throws an error if any values are invalid for the given
|
||||
/// encoder's format.
|
||||
///
|
||||
/// - Parameter encoder: The encoder to write data to.
|
||||
public func encode(to encoder: Encoder) throws
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the value of a key path.
|
||||
public struct MapKeyPath<Upstream, Output> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath: KeyPath<Upstream.Output, Output>
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where Output == S.Input, S : Subscriber, Upstream.Failure == S.Failure
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of two key paths as a tuple.
|
||||
public struct MapKeyPath2<Upstream, Output0, Output1> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (Output0, Output1)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == (Output0, Output1)
|
||||
}
|
||||
|
||||
/// A publisher that publishes the values of three key paths as a tuple.
|
||||
public struct MapKeyPath3<Upstream, Output0, Output1, Output2> : Publisher where Upstream : Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = (Output0, Output1, Output2)
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The key path of a property to publish.
|
||||
public let keyPath0: KeyPath<Upstream.Output, Output0>
|
||||
|
||||
/// The key path of a second property to publish.
|
||||
public let keyPath1: KeyPath<Upstream.Output, Output1>
|
||||
|
||||
/// The key path of a third property to publish.
|
||||
public let keyPath2: KeyPath<Upstream.Output, Output2>
|
||||
|
||||
/// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<S>(subscriber: S) where S : Subscriber, Upstream.Failure == S.Failure, S.Input == (Output0, Output1, Output2)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Returns a publisher that publishes the value of a key path.
|
||||
///
|
||||
/// - Parameter keyPath: The key path of a property on `Output`
|
||||
/// - Returns: A publisher that publishes the value of the key path.
|
||||
public func map<T>(_ keyPath: KeyPath<Self.Output, T>) -> Publishers.MapKeyPath<Self, T>
|
||||
|
||||
/// Returns a publisher that publishes the values of two key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of two key paths as a tuple.
|
||||
public func map<T0, T1>(_ keyPath0: KeyPath<Self.Output, T0>, _ keyPath1: KeyPath<Self.Output, T1>) -> Publishers.MapKeyPath2<Self, T0, T1>
|
||||
|
||||
/// Returns a publisher that publishes the values of three key paths as a tuple.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath0: The key path of a property on `Output`
|
||||
/// - keyPath1: The key path of another property on `Output`
|
||||
/// - keyPath2: The key path of a third property on `Output`
|
||||
/// - Returns: A publisher that publishes the values of three key paths as a tuple.
|
||||
public func map<T0, T1, T2>(_ keyPath0: KeyPath<Self.Output, T0>, _ keyPath1: KeyPath<Self.Output, T1>, _ keyPath2: KeyPath<Self.Output, T2>) -> Publishers.MapKeyPath3<Self, T0, T1, T2>
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
_cancel = cancel
|
||||
}
|
||||
|
||||
public init<CancellableType: Cancellable>(_ canceller: CancellableType) {
|
||||
public init<OtherCancellable: Cancellable>(_ canceller: OtherCancellable) {
|
||||
_cancel = canceller.cancel
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public final class AnyCancellable: Cancellable, Hashable {
|
||||
}
|
||||
|
||||
deinit {
|
||||
cancel()
|
||||
_cancel?()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
///
|
||||
/// Use `AnyPublisher` to wrap a publisher whose type has details you don’t want to expose
|
||||
/// to subscribers or other publishers.
|
||||
public struct AnyPublisher<Output, Failure: Error> {
|
||||
public struct AnyPublisher<Output, Failure: Error>
|
||||
: CustomStringConvertible,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
|
||||
@usableFromInline
|
||||
internal let box: PublisherBoxBase<Output, Failure>
|
||||
@@ -24,6 +27,14 @@ public struct AnyPublisher<Output, Failure: Error> {
|
||||
{
|
||||
box = PublisherBox(base: publisher)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "AnyPublisher"
|
||||
}
|
||||
|
||||
public var playgroundDescription: Any {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher: Publisher {
|
||||
@@ -39,7 +50,7 @@ extension AnyPublisher: Publisher {
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
box.receive(subscriber: subscriber)
|
||||
box.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,9 +72,8 @@ internal class PublisherBoxBase<Output, Failure: Error>: Publisher {
|
||||
|
||||
@usableFromInline
|
||||
internal final class PublisherBox<PublisherType: Publisher>
|
||||
: PublisherBoxBase<PublisherType.Output,
|
||||
PublisherType.Failure> {
|
||||
|
||||
: PublisherBoxBase<PublisherType.Output, PublisherType.Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let base: PublisherType
|
||||
|
||||
@@ -77,6 +87,6 @@ internal final class PublisherBox<PublisherType: Publisher>
|
||||
override internal func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
base.receive(subscriber: subscriber)
|
||||
base.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
//
|
||||
// AnySubject.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
public final class AnySubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private let _box: SubjectBoxBase<Output, Failure>
|
||||
|
||||
public init<SubjectType: Subject>(_ subject: SubjectType)
|
||||
where Output == SubjectType.Output, Failure == SubjectType.Failure
|
||||
{
|
||||
_box = SubjectBox(base: subject)
|
||||
}
|
||||
|
||||
public init(
|
||||
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
|
||||
_ send: @escaping (Output) -> Void,
|
||||
_ sendCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
|
||||
) {
|
||||
_box = ClosureBasedSubject(subscribe, send, sendCompletion)
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
_box.receive(subscriber: subscriber)
|
||||
}
|
||||
|
||||
public func send(_ value: Output) {
|
||||
_box.send(value)
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_box.send(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing base class. Its concrete subclass is generic over the underlying
|
||||
/// publisher.
|
||||
private class SubjectBoxBase<Output, Failure: Error>: Subject {
|
||||
|
||||
func send(_ value: Output) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<Failure>) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
private final class SubjectBox<SubjectType: Subject>
|
||||
: SubjectBoxBase<SubjectType.Output,
|
||||
SubjectType.Failure> {
|
||||
|
||||
private let base: SubjectType
|
||||
|
||||
init(base: SubjectType) {
|
||||
self.base = base
|
||||
}
|
||||
|
||||
override func send(_ value: Output) {
|
||||
base.send(value)
|
||||
}
|
||||
|
||||
override func send(completion: Subscribers.Completion<Failure>) {
|
||||
base.send(completion: completion)
|
||||
}
|
||||
|
||||
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
base.receive(subscriber: subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ClosureBasedSubject<Output, Failure: Error>
|
||||
: SubjectBoxBase<Output, Failure>
|
||||
{
|
||||
|
||||
private let _subscribe: (AnySubscriber<Output, Failure>) -> Void
|
||||
private let _receive: (Output) -> Void
|
||||
private let _receiveCompletion: (Subscribers.Completion<Failure>) -> Void
|
||||
|
||||
init(
|
||||
_ subscribe: @escaping (AnySubscriber<Output, Failure>) -> Void,
|
||||
_ receive: @escaping (Output) -> Void,
|
||||
_ receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void
|
||||
) {
|
||||
_subscribe = subscribe
|
||||
_receive = receive
|
||||
_receiveCompletion = receiveCompletion
|
||||
}
|
||||
|
||||
override func send(_ value: Output) {
|
||||
_receive(value)
|
||||
}
|
||||
|
||||
override func send(completion: Subscribers.Completion<Failure>) {
|
||||
_receiveCompletion(completion)
|
||||
}
|
||||
|
||||
override func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
_subscribe(AnySubscriber(subscriber))
|
||||
}
|
||||
}
|
||||
@@ -15,32 +15,65 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
CustomReflectable,
|
||||
CustomPlaygroundDisplayConvertible
|
||||
{
|
||||
private let _box: SubscriberBoxBase<Input, Failure>
|
||||
@usableFromInline
|
||||
internal let box: AnySubscriberBase<Input, Failure>
|
||||
|
||||
@usableFromInline
|
||||
internal let descriptionThunk: () -> String
|
||||
|
||||
@usableFromInline
|
||||
internal let customMirrorThunk: () -> Mirror
|
||||
|
||||
@usableFromInline
|
||||
internal let playgroundDescriptionThunk: () -> Any
|
||||
|
||||
public let combineIdentifier: CombineIdentifier
|
||||
|
||||
public var description: String { return _box.description }
|
||||
public var description: String { return descriptionThunk() }
|
||||
|
||||
public var customMirror: Mirror { return _box.customMirror }
|
||||
public var customMirror: Mirror { return customMirrorThunk() }
|
||||
|
||||
/// A custom playground description for this instance.
|
||||
public var playgroundDescription: Any { return description }
|
||||
public var playgroundDescription: Any { return playgroundDescriptionThunk() }
|
||||
|
||||
/// Creates a type-erasing subscriber to wrap an existing subscriber.
|
||||
///
|
||||
/// - Parameter s: The subscriber to type-erase.
|
||||
public init<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
|
||||
where Input == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Input == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
_box = SubscriberBox(base: subscriber)
|
||||
combineIdentifier = subscriber.combineIdentifier
|
||||
|
||||
box = AnySubscriberBox(subscriber)
|
||||
|
||||
if let description = subscriber as? CustomStringConvertible {
|
||||
descriptionThunk = { description.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
descriptionThunk = { fixedDescription }
|
||||
}
|
||||
|
||||
customMirrorThunk = {
|
||||
(subscriber as? CustomReflectable)?.customMirror
|
||||
?? Mirror(subscriber, children: EmptyCollection())
|
||||
}
|
||||
|
||||
if let playgroundDescription = subscriber as? CustomPlaygroundDisplayConvertible {
|
||||
playgroundDescriptionThunk = { playgroundDescription.playgroundDescription }
|
||||
} else if let desccription = subscriber as? CustomStringConvertible {
|
||||
playgroundDescriptionThunk = { desccription.description }
|
||||
} else {
|
||||
let fixedDescription = String(describing: type(of: subscriber))
|
||||
playgroundDescriptionThunk = { fixedDescription }
|
||||
}
|
||||
}
|
||||
|
||||
public init<SubjectType: Subject>(_ subject: SubjectType)
|
||||
where Input == SubjectType.Output, Failure == SubjectType.Failure
|
||||
public init<Subject: OpenCombine.Subject>(_ subject: Subject)
|
||||
where Input == Subject.Output, Failure == Subject.Failure
|
||||
{
|
||||
_box = SubjectSubscriber(subject)
|
||||
combineIdentifier = CombineIdentifier(_box)
|
||||
self.init(SubjectSubscriber(subject))
|
||||
}
|
||||
|
||||
/// Creates a type-erasing subscriber that executes the provided closures.
|
||||
@@ -52,140 +85,189 @@ public struct AnySubscriber<Input, Failure: Error>: Subscriber,
|
||||
/// the publisher.
|
||||
/// - receiveCompletion: A closure to execute when the subscriber receives
|
||||
/// a completion callback from the publisher.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public init(receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
|
||||
_box = ClosureBasedSubscriber(receiveSubscription: receiveSubscription,
|
||||
receiveValue: receiveValue,
|
||||
receiveCompletion: receiveCompletion)
|
||||
|
||||
box = ClosureBasedAnySubscriber(
|
||||
receiveSubscription ?? { _ in },
|
||||
receiveValue ?? { _ in .none },
|
||||
receiveCompletion ?? { _ in }
|
||||
)
|
||||
|
||||
combineIdentifier = CombineIdentifier()
|
||||
descriptionThunk = { "Anonymous AnySubscriber" }
|
||||
customMirrorThunk = { Mirror(reflecting: "Anonymous AnySubscriber") }
|
||||
playgroundDescriptionThunk = { "Anonymous AnySubscriber" }
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(subscription: Subscription) {
|
||||
_box.receive(subscription: subscription)
|
||||
box.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(_ value: Input) -> Subscribers.Demand {
|
||||
return _box.receive(value)
|
||||
return box.receive(value)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public func receive(completion: Subscribers.Completion<Failure>) {
|
||||
_box.receive(completion: completion)
|
||||
box.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erasing base class. Its concrete subclass is generic over the underlying
|
||||
/// publisher.
|
||||
internal class SubscriberBoxBase<Input, Failure: Error>: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable {
|
||||
/// subscriber.
|
||||
@usableFromInline
|
||||
internal class AnySubscriberBase<Input, Failure: Error>: Subscriber {
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
internal init() {}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(subscription: Subscription) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
@usableFromInline
|
||||
internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
internal var description: String { return "AnySubscriber" }
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
return Mirror(combineIdentifier, children: EmptyCollection())
|
||||
}
|
||||
}
|
||||
|
||||
internal final class SubscriberBox<SubscriberType: Subscriber>
|
||||
: SubscriberBoxBase<SubscriberType.Input, SubscriberType.Failure>
|
||||
@usableFromInline
|
||||
internal final class AnySubscriberBox<Base: Subscriber>
|
||||
: AnySubscriberBase<Base.Input, Base.Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let base: Base
|
||||
|
||||
private let base: SubscriberType
|
||||
|
||||
internal init(base: SubscriberType) {
|
||||
@inlinable
|
||||
internal init(_ base: Base) {
|
||||
self.base = base
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(subscription: Subscription) {
|
||||
base.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
@inlinable
|
||||
override internal func receive(_ input: Base.Input) -> Subscribers.Demand {
|
||||
return base.receive(input)
|
||||
}
|
||||
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
@inlinable
|
||||
override internal func receive(completion: Subscribers.Completion<Base.Failure>) {
|
||||
base.receive(completion: completion)
|
||||
}
|
||||
|
||||
override internal var customMirror: Mirror { return Mirror(reflecting: base) }
|
||||
|
||||
override internal var description: String { return String(describing: base) }
|
||||
}
|
||||
|
||||
internal final class ClosureBasedSubscriber<Input, Failure: Error>
|
||||
: SubscriberBoxBase<Input, Failure>
|
||||
@usableFromInline
|
||||
internal final class ClosureBasedAnySubscriber<Input, Failure: Error>
|
||||
: AnySubscriberBase<Input, Failure>
|
||||
{
|
||||
@usableFromInline
|
||||
internal let receiveSubscriptionThunk: (Subscription) -> Void
|
||||
|
||||
private let _receiveSubscription: ((Subscription) -> Void)?
|
||||
private let _receiveValue: ((Input) -> Subscribers.Demand)?
|
||||
private let _receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)?
|
||||
@usableFromInline
|
||||
internal let receiveValueThunk: (Input) -> Subscribers.Demand
|
||||
|
||||
internal init(receiveSubscription: ((Subscription) -> Void)? = nil,
|
||||
receiveValue: ((Input) -> Subscribers.Demand)? = nil,
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil) {
|
||||
_receiveSubscription = receiveSubscription
|
||||
_receiveValue = receiveValue
|
||||
_receiveCompletion = receiveCompletion
|
||||
@usableFromInline
|
||||
internal let receiveCompletionThunk: (Subscribers.Completion<Failure>) -> Void
|
||||
|
||||
@inlinable
|
||||
internal init(_ rcvSubscription: @escaping (Subscription) -> Void,
|
||||
_ rcvValue: @escaping (Input) -> Subscribers.Demand,
|
||||
_ rcvCompletion: @escaping (Subscribers.Completion<Failure>) -> Void) {
|
||||
receiveSubscriptionThunk = rcvSubscription
|
||||
receiveValueThunk = rcvValue
|
||||
receiveCompletionThunk = rcvCompletion
|
||||
}
|
||||
|
||||
@inlinable
|
||||
deinit {}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(subscription: Subscription) {
|
||||
_receiveSubscription?(subscription)
|
||||
receiveSubscriptionThunk(subscription)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return _receiveValue?(input) ?? .none
|
||||
return receiveValueThunk(input)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
_receiveCompletion?(completion)
|
||||
receiveCompletionThunk(completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal final class SubjectSubscriber<SubjectType: Subject>
|
||||
: SubscriberBoxBase<SubjectType.Output, SubjectType.Failure>
|
||||
internal final class SubjectSubscriber<Downstream: Subject>
|
||||
: Subscriber,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable,
|
||||
Subscription
|
||||
{
|
||||
internal var parent: SubjectType?
|
||||
internal var downstreamSubject: Downstream?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal init(_ parent: SubjectType) {
|
||||
self.parent = parent
|
||||
internal init(_ parent: Downstream) {
|
||||
self.downstreamSubject = parent
|
||||
}
|
||||
|
||||
override internal func receive(subscription: Subscription) {
|
||||
internal func receive(subscription: Subscription) {
|
||||
guard upstreamSubscription == nil else { return }
|
||||
upstreamSubscription = subscription
|
||||
subscription.request(.unlimited)
|
||||
downstreamSubject?.send(subscription: self)
|
||||
}
|
||||
|
||||
override internal func receive(_ input: Input) -> Subscribers.Demand {
|
||||
parent?.send(input)
|
||||
internal func receive(_ input: Downstream.Output) -> Subscribers.Demand {
|
||||
downstreamSubject?.send(input)
|
||||
return .none
|
||||
}
|
||||
|
||||
override internal func receive(completion: Subscribers.Completion<Failure>) {
|
||||
parent?.send(completion: completion)
|
||||
internal func receive(completion: Subscribers.Completion<Downstream.Failure>) {
|
||||
downstreamSubject?.send(completion: completion)
|
||||
}
|
||||
|
||||
override internal var description: String { return "Subject" }
|
||||
internal var description: String { return "Subject" }
|
||||
|
||||
override internal var customMirror: Mirror {
|
||||
internal var customMirror: Mirror {
|
||||
let children: [(label: String?, value: Any)] = [
|
||||
(label: "parent", value: parent as Any),
|
||||
(label: "downstreamSubject", value: downstreamSubject as Any),
|
||||
(label: "upstreamSubscription", value: upstreamSubscription as Any)
|
||||
]
|
||||
return Mirror(self, children: children)
|
||||
}
|
||||
|
||||
internal func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
internal func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
downstreamSubject = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// Cancelable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
public protocol Cancellable {
|
||||
|
||||
/// Cancel the activity.
|
||||
func cancel()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Cancellable.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.06.2019.
|
||||
//
|
||||
|
||||
/// A protocol indicating that an activity or action may be canceled.
|
||||
///
|
||||
/// Calling `cancel()` frees up any allocated resources. It also stops side effects such
|
||||
/// as timers, network access, or disk I/O.
|
||||
public protocol Cancellable {
|
||||
|
||||
/// Cancel the activity.
|
||||
func cancel()
|
||||
}
|
||||
|
||||
extension Cancellable {
|
||||
|
||||
/// Stores this Cancellable in the specified collection.
|
||||
/// Parameters:
|
||||
/// - collection: The collection to store this Cancellable.
|
||||
public func store<Cancellables: RangeReplaceableCollection>(
|
||||
in collection: inout Cancellables
|
||||
) where Cancellables.Element == AnyCancellable {
|
||||
AnyCancellable(self).store(in: &collection)
|
||||
}
|
||||
|
||||
/// Stores this Cancellable in the specified set.
|
||||
/// Parameters:
|
||||
/// - collection: The set to store this Cancellable.
|
||||
public func store(in set: inout Set<AnyCancellable>) {
|
||||
AnyCancellable(self).store(in: &set)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
|
||||
private var _completion: Subscribers.Completion<Failure>?
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
|
||||
/// The value wrapped by this subject, published as a new element whenever it changes.
|
||||
public var value: Output {
|
||||
didSet {
|
||||
@@ -30,16 +34,36 @@ public final class CurrentValueSubject<Output, Failure: Error>: Subject {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
|
||||
|
||||
_lock.do {
|
||||
_subscriptions.append(subscription)
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
subscriber.receive(subscription: subscription)
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Output == Subscriber.Input, Failure == Subscriber.Failure
|
||||
{
|
||||
_lock.do {
|
||||
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
@@ -102,7 +126,7 @@ extension CurrentValueSubject {
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
guard demand > 0 else { return }
|
||||
precondition(demand > 0)
|
||||
_parent?._lock.do {
|
||||
if !_delivered, let value = _parent?.value {
|
||||
_offer(value)
|
||||
@@ -111,6 +135,7 @@ extension CurrentValueSubject {
|
||||
} else {
|
||||
_demand = demand
|
||||
}
|
||||
_parent?.hasAnyDownstreamDemand = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,3 +144,7 @@ extension CurrentValueSubject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CurrentValueSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "CurrentValueSubject" }
|
||||
}
|
||||
|
||||
@@ -18,24 +18,49 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
// TODO: Combine uses bag data structure
|
||||
private var _subscriptions: [Conduit] = []
|
||||
|
||||
internal var upstreamSubscriptions: [Subscription] = []
|
||||
|
||||
internal var hasAnyDownstreamDemand = false
|
||||
|
||||
public init() {}
|
||||
|
||||
deinit {
|
||||
for subscription in _subscriptions {
|
||||
subscription._downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func send(subscription: Subscription) {
|
||||
_lock.do {
|
||||
upstreamSubscriptions.append(subscription)
|
||||
if hasAnyDownstreamDemand {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
let subscription = Conduit(parent: self, downstream: AnySubscriber(subscriber))
|
||||
|
||||
_lock.do {
|
||||
_subscriptions.append(subscription)
|
||||
}
|
||||
if let completion = _completion {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: completion)
|
||||
return
|
||||
} else {
|
||||
let subscription = Conduit(parent: self,
|
||||
downstream: AnySubscriber(subscriber))
|
||||
|
||||
subscriber.receive(subscription: subscription)
|
||||
_subscriptions.append(subscription)
|
||||
subscriber.receive(subscription: subscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ input: Output) {
|
||||
_lock.do {
|
||||
for subscription in _subscriptions
|
||||
where !subscription.isCompleted && subscription._demand > 0
|
||||
where !subscription._isCompleted && subscription._demand > 0
|
||||
{
|
||||
let newDemand = subscription._downstream?.receive(input) ?? .none
|
||||
subscription._demand += newDemand
|
||||
@@ -45,13 +70,23 @@ public final class PassthroughSubject<Output, Failure: Error>: Subject {
|
||||
}
|
||||
|
||||
public func send(completion: Subscribers.Completion<Failure>) {
|
||||
_completion = completion
|
||||
_lock.do {
|
||||
_completion = completion
|
||||
for subscriber in _subscriptions {
|
||||
subscriber._receive(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func _acknowledgeDownstreamDemand() {
|
||||
_lock.do {
|
||||
guard !hasAnyDownstreamDemand else { return }
|
||||
hasAnyDownstreamDemand = true
|
||||
for subscription in upstreamSubscriptions {
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject {
|
||||
@@ -64,7 +99,7 @@ extension PassthroughSubject {
|
||||
|
||||
fileprivate var _demand: Subscribers.Demand = .none
|
||||
|
||||
var isCompleted: Bool {
|
||||
fileprivate var _isCompleted: Bool {
|
||||
return _parent == nil
|
||||
}
|
||||
|
||||
@@ -75,20 +110,26 @@ extension PassthroughSubject {
|
||||
}
|
||||
|
||||
fileprivate func _receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
if !_isCompleted {
|
||||
_parent = nil
|
||||
_downstream?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
fileprivate func request(_ demand: Subscribers.Demand) {
|
||||
precondition(demand > 0, "demand must not be zero")
|
||||
_parent?._lock.do {
|
||||
_demand = demand
|
||||
}
|
||||
_parent?._acknowledgeDownstreamDemand()
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
fileprivate func cancel() {
|
||||
_parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PassthroughSubject.Conduit: CustomStringConvertible {
|
||||
fileprivate var description: String { return "PassthroughSubject" }
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ public protocol Publisher {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
func receive<Subscriber: OpenCombine.Subscriber>(subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
@@ -52,9 +52,19 @@ extension Publisher {
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`. After attaching,
|
||||
/// the subscriber can start to receive values.
|
||||
public func subscribe<SubscriberType: Subscriber>(_ subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
public func subscribe<Subscriber: OpenCombine.Subscriber>(_ subscriber: Subscriber)
|
||||
where Failure == Subscriber.Failure, Output == Subscriber.Input
|
||||
{
|
||||
receive(subscriber: subscriber)
|
||||
}
|
||||
|
||||
public func subscribe<Subject: OpenCombine.Subject>(
|
||||
_ subject: Subject
|
||||
) -> AnyCancellable
|
||||
where Failure == Subject.Failure, Output == Subject.Output
|
||||
{
|
||||
let subscriber = SubjectSubscriber(subject)
|
||||
self.subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Deferred.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/7/19.
|
||||
//
|
||||
|
||||
/// A publisher that awaits subscription before running the supplied closure
|
||||
/// to create a publisher for the new subscriber.
|
||||
public struct Deferred<DeferredPublisher: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = DeferredPublisher.Output
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = DeferredPublisher.Failure
|
||||
|
||||
/// The closure to execute when it receives a subscription.
|
||||
///
|
||||
/// The publisher returned by this closure immediately
|
||||
/// receives the incoming subscription.
|
||||
public let createPublisher: () -> DeferredPublisher
|
||||
|
||||
/// Creates a deferred publisher.
|
||||
///
|
||||
/// - Parameter createPublisher: The closure to execute
|
||||
/// when calling `subscribe(_:)`.
|
||||
public init(createPublisher: @escaping () -> DeferredPublisher) {
|
||||
self.createPublisher = createPublisher
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Output == SubscriberType.Input
|
||||
{
|
||||
let deferredPublisher = createPublisher()
|
||||
deferredPublisher.subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Empty.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
/// A publisher that never publishes any values, and optionally finishes immediately.
|
||||
///
|
||||
/// You can create a ”Never” publisher — one which never sends values and never
|
||||
/// finishes or fails — with the initializer `Empty(completeImmediately: false)`.
|
||||
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
|
||||
/// Creates an empty publisher.
|
||||
///
|
||||
/// - Parameter completeImmediately: A Boolean value that indicates whether
|
||||
/// the publisher should immediately finish.
|
||||
public init(completeImmediately: Bool = true) {
|
||||
self.completeImmediately = completeImmediately
|
||||
}
|
||||
|
||||
/// Creates an empty publisher with the given completion behavior and output and
|
||||
/// failure types.
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failureType: The failure type exposed by this publisher.
|
||||
public init(completeImmediately: Bool = true,
|
||||
outputType: Output.Type,
|
||||
failureType: Failure.Type) {
|
||||
self.init(completeImmediately: completeImmediately)
|
||||
}
|
||||
|
||||
/// A Boolean value that indicates whether the publisher immediately sends
|
||||
/// a completion.
|
||||
///
|
||||
/// If `true`, the publisher finishes immediately after sending a subscription
|
||||
/// to the subscriber. If `false`, it never completes.
|
||||
public let completeImmediately: Bool
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
if completeImmediately {
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Fail.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
/// A publisher that immediately terminates with the specified error.
|
||||
public struct Fail<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// Creates a publisher that immediately terminates with the specified failure.
|
||||
///
|
||||
/// - Parameter error: The failure to send when terminating the publisher.
|
||||
public init(error: Failure) {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
/// Creates publisher with the given output type, that immediately terminates with
|
||||
/// the specified failure.
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
public init(outputType: Output.Type, failure: Failure) {
|
||||
self.error = failure
|
||||
}
|
||||
|
||||
/// The failure to send when terminating the publisher.
|
||||
public let error: Failure
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
extension Fail: Equatable where Failure: Equatable {}
|
||||
@@ -11,8 +11,6 @@
|
||||
/// is also useful when replacing a value with `Catch`.
|
||||
///
|
||||
/// In contrast with `Publishers.Once`, a `Just` publisher cannot fail with an error.
|
||||
/// In contrast with `Publishers.Optional`, a `Just` publisher always produces
|
||||
/// a value.
|
||||
public struct Just<Output>: Publisher {
|
||||
|
||||
public typealias Failure = Never
|
||||
@@ -66,8 +64,8 @@ extension Just {
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(Result { try predicate(output) })
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try predicate(output) })
|
||||
}
|
||||
|
||||
public func contains(where predicate: (Output) -> Bool) -> Just<Bool> {
|
||||
@@ -76,8 +74,8 @@ extension Just {
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(Result { try predicate(output) })
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try predicate(output) })
|
||||
}
|
||||
|
||||
public func collect() -> Just<[Output]> {
|
||||
@@ -90,45 +88,25 @@ extension Just {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Just<Output> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(Result { try areInIncreasingOrder(output, output) })
|
||||
}
|
||||
|
||||
public func count() -> Just<Int> {
|
||||
return .init(1)
|
||||
}
|
||||
|
||||
public func dropFirst(
|
||||
_ count: Int = 1
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional(count > 0 ? nil : output)
|
||||
return .init(count > 0 ? nil : self.output)
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? nil : output)
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? nil : output })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? nil : output)
|
||||
}
|
||||
|
||||
public func first() -> Just<Output> {
|
||||
@@ -137,14 +115,8 @@ extension Just {
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func last() -> Just<Output> {
|
||||
@@ -153,18 +125,12 @@ extension Just {
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Never> {
|
||||
return Publishers.Empty()
|
||||
public func ignoreOutput() -> Empty<Output, Never> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
@@ -175,79 +141,61 @@ extension Just {
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(Result { try transform(output) })
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(Result { try transform(output) })
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Never> {
|
||||
return Publishers.Optional(transform(output))
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(Result { try transform(output) })
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(transform(output))
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(isIncluded(output) ? output : nil)
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(isIncluded(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try isIncluded(output) ? output : nil })
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Never> {
|
||||
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(index == 0 ? output : nil)
|
||||
return .init(index == 0 ? output : nil)
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Never> where RangeExpr.Bound == Int {
|
||||
public func output<RangeExpression: Swift.RangeExpression>(
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(range.lowerBound == 0 ? output : nil)
|
||||
return .init(range.lowerBound == 0 ? output : nil)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(range.contains(0) ? output : nil)
|
||||
// return .init(range.contains(0) ? output : nil)
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Never> {
|
||||
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
return Publishers.Optional(maxLength > 0 ? output : nil)
|
||||
return .init(maxLength > 0 ? output : nil)
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(Result { try predicate(output) ? output : nil })
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(predicate(output) ? output : nil)
|
||||
}
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(output)
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(output)
|
||||
}
|
||||
|
||||
public func mapError<Failure: Error>(
|
||||
_ transform: (Never) -> Failure
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(output)
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(output)
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
@@ -258,9 +206,8 @@ extension Just {
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Error> {
|
||||
return Publishers
|
||||
.Once(Result { try _ = predicate(output, output); return output })
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(Result { try _ = predicate(output, output); return output })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Just<Output> {
|
||||
@@ -275,36 +222,32 @@ extension Just {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Just<Output> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Never> {
|
||||
return Publishers.Once(nextPartialResult(initialResult, output))
|
||||
) -> Result<Accumulator, Never>.OCombine.Publisher {
|
||||
return .init(nextPartialResult(initialResult, output))
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher {
|
||||
return .init(Result { try nextPartialResult(initialResult, output) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Never> {
|
||||
return Publishers.Once(nextPartialResult(initialResult, output))
|
||||
) -> Result<ElementOfResult, Never>.OCombine.Publisher {
|
||||
return .init(nextPartialResult(initialResult, output))
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(Result { try nextPartialResult(initialResult, output) })
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(Result { try nextPartialResult(initialResult, output) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
|
||||
internal var downstream: Downstream
|
||||
internal var downstream: Downstream?
|
||||
internal var upstreamSubscription: Subscription?
|
||||
|
||||
internal var customMirror: Mirror {
|
||||
@@ -20,5 +20,6 @@ internal class OperatorSubscription<Downstream: Subscriber>: CustomReflectable {
|
||||
internal func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
//
|
||||
// Optional.Publisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Optional {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `Optional` with a nested type `Publisher`.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able to write
|
||||
/// `Optional<Int>.Publisher`, because Swift is unable to understand
|
||||
/// which `Publisher` you're referring to.
|
||||
///
|
||||
/// So you have to write `Optional<Int>.OCombine.Publisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
fileprivate let optional: Optional
|
||||
|
||||
fileprivate init(_ optional: Optional) {
|
||||
self.optional = optional
|
||||
}
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
public typealias Output = Wrapped
|
||||
|
||||
public typealias Failure = Never
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let output: Wrapped?
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result,
|
||||
/// or fail with an error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ output: Output?) {
|
||||
self.output = output
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
if let output = output {
|
||||
subscriber.receive(subscription: Inner(value: output,
|
||||
downstream: subscriber))
|
||||
} else {
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an optional value to each subscriber
|
||||
/// exactly once, if the optional has a value.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher may send
|
||||
/// no value before completion.
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Optional" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher: Equatable where Wrapped: Equatable {}
|
||||
|
||||
extension Optional.OCombine.Publisher where Wrapped: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher where Wrapped: Comparable {
|
||||
|
||||
public func min() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Optional<Wrapped>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional.OCombine.Publisher {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Optional<[Output]>.OCombine.Publisher {
|
||||
return .init(self.output.map { [$0] })
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(self.output.flatMap(transform))
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Bool>.OCombine.Publisher {
|
||||
return .init(self.output.map(predicate))
|
||||
}
|
||||
|
||||
public func count() -> Optional<Int>.OCombine.Publisher {
|
||||
return .init(self.output.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return .init(self.output.flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(self.output.flatMap { predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func first() -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func last() -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Empty<Output, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(output.map(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return .init(output.flatMap { index == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func output<RangeExpression: Swift.RangeExpression>(
|
||||
in range: RangeExpression
|
||||
) -> Optional<Output>.OCombine.Publisher where RangeExpression.Bound == Int {
|
||||
let range = range.relative(to: 0 ..< Int.max)
|
||||
precondition(range.lowerBound >= 0, "lowerBould must not be negative")
|
||||
|
||||
// I don't know why, but Combine has this precondition
|
||||
precondition(range.upperBound < .max - 1)
|
||||
return .init(output.flatMap { range.contains(0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
return .init(output.flatMap { maxLength > 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return .init(output.flatMap { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Optional<Accumulator>.OCombine.Publisher {
|
||||
return .init(output.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Optional<ElementOfResult>.OCombine.Publisher {
|
||||
return .init(output.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func replaceEmpty(with output: Output) -> Just<Output> {
|
||||
return .init(self.output ?? output)
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Optional<Output>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Publishers.Count.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/25/19.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes the number of elements received
|
||||
/// from the upstream publisher.
|
||||
public struct Count<Upstream: Publisher>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Int
|
||||
|
||||
/// The kind of errors this publisher might publish.
|
||||
///
|
||||
/// Use `Never` if this `Publisher` does not publish errors.
|
||||
public typealias Failure = Upstream.Failure
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Upstream.Failure == SubscriberType.Failure,
|
||||
SubscriberType.Input == Output
|
||||
{
|
||||
let count = _Count<Upstream, SubscriberType>(downstream: subscriber)
|
||||
upstream.subscribe(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Publishes the number of elements received from the upstream publisher.
|
||||
///
|
||||
/// - Returns: A publisher that consumes all elements until the upstream publisher
|
||||
/// finishes, then emits a single value with the total number of elements received.
|
||||
public func count() -> Publishers.Count<Self> {
|
||||
return Publishers.Count(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class _Count<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Downstream.Input == Int,
|
||||
Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
|
||||
typealias Input = Upstream.Output
|
||||
typealias Output = Int
|
||||
typealias Failure = Downstream.Failure
|
||||
|
||||
private var _count = 0
|
||||
|
||||
var description: String { return "Count" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
upstreamSubscription?.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
_count += 1
|
||||
return .none
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Upstream.Failure>) {
|
||||
if case .finished = completion {
|
||||
_ = downstream.receive(_count)
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ extension Publishers {
|
||||
downstream: subscriber,
|
||||
decoder: _decoder
|
||||
)
|
||||
upstream.receive(subscriber: decodeSubscriber)
|
||||
upstream.subscribe(decodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ extension Publishers {
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,14 +57,13 @@ extension Publishers {
|
||||
where Output == SubscriberType.Input, SubscriberType.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, predicate: catching(predicate))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
@@ -74,22 +73,30 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
|
||||
/// The predicate is reset to `nil` as soon as it returns `false`.
|
||||
var predicate: Predicate?
|
||||
|
||||
var demand: Subscribers.Demand = .none
|
||||
var isCompleted = false
|
||||
|
||||
init(downstream: Downstream, predicate: @escaping Predicate) {
|
||||
self.predicate = predicate
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
var description: String { return "DropWhile" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
|
||||
guard upstreamSubscription != nil else {
|
||||
return .none
|
||||
}
|
||||
|
||||
guard let predicate = self.predicate else {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
// NOTE: until the predicate returns false, we will ask the upstream publisher
|
||||
// for elements one by one, no matter how much elements the downstream subscriber
|
||||
// requests.
|
||||
// for elements one by one.
|
||||
//
|
||||
// However, IF the downstream requests anything, we accumulate this demand in the
|
||||
// `demand` property so that later we can provide the downstream with the correct
|
||||
@@ -97,49 +104,30 @@ private class _DropWhile<Upstream: Publisher, Downstream: Subscriber>
|
||||
//
|
||||
// As soon as the predicate returns false, we switch to the mode where
|
||||
// we just forward all the requests from the downstream to the upstream.
|
||||
subscription.request(.max(1))
|
||||
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
|
||||
guard let predicate = self.predicate else {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
switch predicate(input) {
|
||||
case .success(true):
|
||||
// See the NOTE above to understand why we return .max(1)
|
||||
return .max(1)
|
||||
case .success(false):
|
||||
// Okay, we hit the first element not satisfying the predicate,
|
||||
// from now on we just republish the values to the downstream.
|
||||
self.predicate = nil
|
||||
|
||||
// The demand that the downstream has requested has been accumulated in the
|
||||
// `demand` property. Now it's time to pay the debt.
|
||||
//
|
||||
// Subtracting 1 for the current value.
|
||||
return demand + downstream.receive(input) - 1
|
||||
return downstream.receive(input)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(error))
|
||||
isCompleted = true
|
||||
cancel()
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if predicate == nil {
|
||||
// If predicate is nil, that means that we have already received a value
|
||||
// that doesn't satisfy the predicate, hence we're in the state where we
|
||||
// just forward each request to the upstream.
|
||||
upstreamSubscription?.request(demand)
|
||||
} else {
|
||||
// Otherwise, as mentioned in the NOTE above, we accumulate all the demand
|
||||
// requested by the downstream until the predicate returns false.
|
||||
self.demand += demand
|
||||
}
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
upstreamSubscription?.cancel()
|
||||
upstreamSubscription = nil
|
||||
// Don't zero out downstream, that's what Combine does (probably a bug)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,10 +135,17 @@ extension Publishers.DropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
var description: String { return "DropWhile" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else {
|
||||
assertionFailure("unreachable")
|
||||
return
|
||||
}
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
@@ -160,10 +155,14 @@ extension Publishers.TryDropWhile {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _DropWhile<Upstream, Downstream>,
|
||||
Subscriber
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Upstream.Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
var description: String { return "TryDropWhile" }
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
guard !isCompleted else { return }
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//
|
||||
// Publishers.Empty.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that never publishes any values, and optionally finishes immediately.
|
||||
///
|
||||
/// You can create a ”Never” publisher — one which never sends values and never
|
||||
/// finishes or fails — with the initializer `Empty(completeImmediately: false)`.
|
||||
public struct Empty<Output, Failure: Error>: Publisher, Equatable {
|
||||
|
||||
/// Creates an empty publisher.
|
||||
///
|
||||
/// - Parameter completeImmediately: A Boolean value that indicates whether
|
||||
/// the publisher should immediately finish.
|
||||
public init(completeImmediately: Bool = true) {
|
||||
self.completeImmediately = completeImmediately
|
||||
}
|
||||
|
||||
/// Creates an empty publisher with the given completion behavior and output and
|
||||
/// failure types.
|
||||
///
|
||||
/// Use this initializer to connect the empty publisher to subscribers or other
|
||||
/// publishers that have specific output and failure types.
|
||||
/// - Parameters:
|
||||
/// - completeImmediately: A Boolean value that indicates whether the publisher
|
||||
/// should immediately finish.
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failureType: The failure type exposed by this publisher.
|
||||
public init(completeImmediately: Bool = true,
|
||||
outputType: Output.Type,
|
||||
failureType: Failure.Type) {
|
||||
self.init(completeImmediately: completeImmediately)
|
||||
}
|
||||
|
||||
/// A Boolean value that indicates whether the publisher immediately sends
|
||||
/// a completion.
|
||||
///
|
||||
/// If `true`, the publisher finishes immediately after sending a subscription
|
||||
/// to the subscriber. If `false`, it never completes.
|
||||
public let completeImmediately: Bool
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
if completeImmediately {
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ extension Publishers {
|
||||
downstream: subscriber,
|
||||
encoder: encoder
|
||||
)
|
||||
upstream.receive(subscriber: encodeSubscriber)
|
||||
upstream.subscribe(encodeSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// Publishers.Fail.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 19.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that immediately terminates with the specified error.
|
||||
public struct Fail<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// Creates a publisher that immediately terminates with the specified failure.
|
||||
///
|
||||
/// - Parameter error: The failure to send when terminating the publisher.
|
||||
public init(error: Failure) {
|
||||
self.error = error
|
||||
}
|
||||
|
||||
/// Creates publisher with the given output type, that immediately terminates with
|
||||
/// the specified failure.
|
||||
///
|
||||
/// Use this initializer to create a `Fail` publisher that can work with
|
||||
/// subscribers or publishers that expect a given output type.
|
||||
/// - Parameters:
|
||||
/// - outputType: The output type exposed by this publisher.
|
||||
/// - failure: The failure to send when terminating the publisher.
|
||||
public init(outputType: Output.Type, failure: Failure) {
|
||||
self.error = failure
|
||||
}
|
||||
|
||||
/// The failure to send when terminating the publisher.
|
||||
public let error: Failure
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Fail: Equatable where Failure: Equatable {}
|
||||
@@ -48,6 +48,12 @@ extension Publishers {
|
||||
|
||||
/// The closure that transforms elements from the upstream publisher.
|
||||
public let transform: (Upstream.Output) -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) -> Output) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
}
|
||||
|
||||
/// A publisher that transforms all elements from the upstream publisher
|
||||
@@ -62,6 +68,12 @@ extension Publishers {
|
||||
/// The error-throwing closure that transforms elements from
|
||||
/// the upstream publisher.
|
||||
public let transform: (Upstream.Output) throws -> Output
|
||||
|
||||
public init(upstream: Upstream,
|
||||
transform: @escaping (Upstream.Output) throws -> Output) {
|
||||
self.upstream = upstream
|
||||
self.transform = transform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +82,7 @@ extension Publishers.Map {
|
||||
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
@@ -92,7 +104,7 @@ extension Publishers.TryMap {
|
||||
where Output == Downstream.Input, Downstream.Failure == Error
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, transform: catching(transform))
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
public func map<Result>(
|
||||
@@ -109,9 +121,7 @@ extension Publishers.TryMap {
|
||||
}
|
||||
|
||||
private class _Map<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
: OperatorSubscription<Downstream>
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
@@ -128,13 +138,6 @@ private class _Map<Upstream: Publisher, Downstream: Subscriber>
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
var description: String { return "Map" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
switch _transform?(input) {
|
||||
case .success(let output)?:
|
||||
@@ -147,30 +150,26 @@ private class _Map<Upstream: Publisher, Downstream: Subscriber>
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
upstreamSubscription?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Map {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Downstream.Failure == Upstream.Failure
|
||||
{
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
var description: String { return "Map" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,14 +177,32 @@ extension Publishers.TryMap {
|
||||
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: _Map<Upstream, Downstream>,
|
||||
Subscription,
|
||||
CustomStringConvertible,
|
||||
Subscriber
|
||||
where Downstream.Failure == Error
|
||||
{
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
if !isCompleted {
|
||||
_transform = nil
|
||||
downstream.receive(completion: completion.eraseError())
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
_transform = nil
|
||||
super.cancel()
|
||||
}
|
||||
|
||||
var description: String { return "TryMap" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// Publishers.MapError.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that converts any failure from the
|
||||
/// upstream publisher into a new error.
|
||||
public struct MapError<Upstream: Publisher, Failure: Error>: Publisher {
|
||||
|
||||
/// The kind of values published by this publisher.
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// The closure that converts the upstream failure into a new error.
|
||||
public let transform: (Upstream.Failure) -> Failure
|
||||
|
||||
public init(upstream: Upstream, _ map: @escaping (Upstream.Failure) -> Failure) {
|
||||
self.upstream = upstream
|
||||
self.transform = map
|
||||
}
|
||||
|
||||
/// This function is called to attach the specified `Subscriber`
|
||||
/// to this `Publisher` by `subscribe(_:)`
|
||||
///
|
||||
/// - SeeAlso: `subscribe(_:)`
|
||||
/// - Parameters:
|
||||
/// - subscriber: The subscriber to attach to this `Publisher`.
|
||||
/// once attached it can begin to receive values.
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure,
|
||||
Upstream.Output == SubscriberType.Input
|
||||
{
|
||||
let mapErrorSubscriber = _MapError<Upstream, SubscriberType>(
|
||||
downstream: subscriber,
|
||||
transform: transform
|
||||
)
|
||||
upstream.subscribe(mapErrorSubscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Converts any failure from the upstream publisher into a new error.
|
||||
///
|
||||
/// Until the upstream publisher finishes normally or fails with an error,
|
||||
/// the returned publisher republishes all the elements it receives.
|
||||
///
|
||||
/// - Parameter transform: A closure that takes the upstream failure as a
|
||||
/// parameter and returns a new error for the publisher to terminate with.
|
||||
/// - Returns: A publisher that replaces any upstream failure with a
|
||||
/// new error produced by the `transform` closure.
|
||||
public func mapError<NewFailure: Error>(
|
||||
_ transform: @escaping (Self.Failure) -> NewFailure
|
||||
) -> Publishers.MapError<Self, NewFailure>
|
||||
{
|
||||
return Publishers.MapError(upstream: self, transform)
|
||||
}
|
||||
}
|
||||
|
||||
private final class _MapError<Upstream: Publisher, Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
typealias Output = Downstream.Input
|
||||
|
||||
private let _transform: (Upstream.Failure) -> Downstream.Failure
|
||||
|
||||
var description: String { return "MapError" }
|
||||
|
||||
init(downstream: Downstream,
|
||||
transform: @escaping (Upstream.Failure) -> Downstream.Failure) {
|
||||
self._transform = transform
|
||||
super.init(downstream: downstream)
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
switch completion {
|
||||
case .finished:
|
||||
downstream.receive(completion: .finished)
|
||||
case .failure(let error):
|
||||
downstream.receive(completion: .failure(_transform(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,25 @@
|
||||
// Created by Sergej Jaskiewicz on 14.06.2019.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
public func multicast<SubjectType: Subject>(
|
||||
_ createSubject: @escaping () -> SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
where Failure == SubjectType.Failure, Output == SubjectType.Output
|
||||
{
|
||||
return Publishers.Multicast(upstream: self, createSubject: createSubject)
|
||||
}
|
||||
|
||||
public func multicast<SubjectType: Subject>(
|
||||
subject: SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
where Failure == SubjectType.Failure, Output == SubjectType.Output
|
||||
{
|
||||
return multicast { subject }
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers {
|
||||
|
||||
public final class Multicast<Upstream: Publisher, SubjectType: Subject>
|
||||
@@ -28,11 +47,11 @@ extension Publishers {
|
||||
self.createSubject = createSubject
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubjectType.Failure == SubscriberType.Failure,
|
||||
SubjectType.Output == SubscriberType.Input
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where SubjectType.Failure == Downstream.Failure,
|
||||
SubjectType.Output == Downstream.Input
|
||||
{
|
||||
_subject.subscribe(subscriber)
|
||||
_subject.subscribe(Inner(downstream: subscriber))
|
||||
}
|
||||
|
||||
public func connect() -> Cancellable {
|
||||
@@ -42,27 +61,41 @@ extension Publishers {
|
||||
upstream.subscribe(subscriber)
|
||||
|
||||
return AnyCancellable {
|
||||
subscriber.parent = nil
|
||||
subscriber.downstreamSubject = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher {
|
||||
extension Publishers.Multicast {
|
||||
|
||||
public func multicast<SubjectType: Subject>(
|
||||
_ createSubject: @escaping () -> SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
where Failure == SubjectType.Failure, Output == SubjectType.Output
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible,
|
||||
Subscription
|
||||
where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
|
||||
{
|
||||
return Publishers.Multicast(upstream: self, createSubject: createSubject)
|
||||
}
|
||||
typealias Input = Upstream.Output
|
||||
typealias Failure = Upstream.Failure
|
||||
|
||||
public func multicast<SubjectType: Subject>(
|
||||
subject: SubjectType
|
||||
) -> Publishers.Multicast<Self, SubjectType>
|
||||
where Failure == SubjectType.Failure, Output == SubjectType.Output
|
||||
{
|
||||
return multicast { subject }
|
||||
var description: String { return "Multicast" }
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
upstreamSubscription = subscription
|
||||
downstream.receive(subscription: self)
|
||||
}
|
||||
|
||||
func receive(_ input: Input) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {
|
||||
downstream.receive(completion: completion)
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
upstreamSubscription?.request(demand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,387 +0,0 @@
|
||||
//
|
||||
// Publishers.Once.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
|
||||
/// sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
|
||||
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
|
||||
/// value (unless it terminates with an error).
|
||||
public struct Once<Output, Failure: Error>: Publisher {
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result<Output, Failure>
|
||||
|
||||
/// Creates a publisher that delivers the specified result.
|
||||
///
|
||||
/// If the result is `.success`, the `Once` publisher sends the specified output
|
||||
/// to all subscribers and finishes normally. If the result is `.failure`, then
|
||||
/// the publisher fails immediately with the specified error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result<Output, Failure>) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
/// Creates a publisher that sends the specified output to all subscribers and
|
||||
/// finishes normally.
|
||||
///
|
||||
/// - Parameter output: The output to deliver to each subscriber.
|
||||
public init(_ output: Output) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
/// Creates a publisher that immediately terminates upon subscription with
|
||||
/// the given failure.
|
||||
///
|
||||
/// - Parameter failure: The failure to send when terminating.
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Output, SubscriberType.Failure == Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Once" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once: Equatable where Output: Equatable, Failure: Equatable {}
|
||||
|
||||
extension Publishers.Once where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once where Output: Comparable {
|
||||
|
||||
public func min() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
return Publishers.Once(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
return Publishers.Once(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Once<[Output], Failure> {
|
||||
return Publishers.Once(result.map { [$0] })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output
|
||||
) -> Bool) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Once<Int, Failure> {
|
||||
return Publishers.Once(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional((try? result.get()).flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? nil : $0 })
|
||||
}
|
||||
|
||||
public func first() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func last() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try isIncluded($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
|
||||
return Publishers.Empty()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Failure> {
|
||||
return Publishers.Once(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Publishers.Once<Output, TransformedFailure> {
|
||||
return Publishers.Once(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(result.map { index == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(
|
||||
result.map { range.lowerBound == 0 ? $0 : nil }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(
|
||||
// result.map { range.contains(0) ? $0 : nil }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
return Publishers.Optional(
|
||||
result.map { maxLength == 0 ? $0 : nil }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually should be the following:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.tryMap { try predicate($0) ? $0 : nil })
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Once<Output, Error> {
|
||||
return Publishers.Once(result.mapError { $0 })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Publishers.Once<Output, Never> {
|
||||
return Publishers.Once(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(with output: Output) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Publishers.Once<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Failure> {
|
||||
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Failure> {
|
||||
return Publishers.Once(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Once<ElementOfResult, Error> {
|
||||
return Publishers.Once(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Once where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Once<Output, Failure> {
|
||||
return Publishers.Once(result.success)
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
//
|
||||
// Publishers.Optional.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that publishes an optional value to each subscriber exactly once, if
|
||||
/// the optional has a value.
|
||||
///
|
||||
/// If `result` is `.success`, and the value is non-nil, then `Optional` waits until
|
||||
/// receiving a request for at least 1 value before sending the output. If `result` is
|
||||
/// `.failure`, then `Optional` sends the failure immediately upon subscription.
|
||||
/// If `result` is `.success` and the value is nil, then `Optional` sends `.finished`
|
||||
/// immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, an `Optional` publisher can send an error.
|
||||
/// In contrast with `Once`, an `Optional` publisher can send zero values and finish
|
||||
/// normally, or send zero values and fail with an error.
|
||||
public struct Optional<Output, Failure: Error>: Publisher {
|
||||
// swiftlint:disable:previous syntactic_sugar
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result<Output?, Failure>
|
||||
|
||||
/// Creates a publisher to emit the optional value of a successful result, or fail
|
||||
/// with an error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result<Output?, Failure>) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
public init(_ output: Output?) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Output == SubscriberType.Input, Failure == SubscriberType.Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value?):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .success(nil):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .finished)
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Optional" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional: Equatable where Output: Equatable, Failure: Equatable {}
|
||||
|
||||
extension Publishers.Optional where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional where Output: Comparable {
|
||||
|
||||
public func min() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Optional<[Output], Failure> {
|
||||
return Publishers.Optional(result.map { $0.map { [$0] } })
|
||||
}
|
||||
|
||||
public func compactMap<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap(transform) })
|
||||
}
|
||||
|
||||
public func tryCompactMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult?
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.flatMap(transform) })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Bool, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Bool, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(predicate) })
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Optional<Int, Failure> {
|
||||
return Publishers.Optional(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func dropFirst(_ count: Int = 1) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(count >= 0, "count must not be negative")
|
||||
return Publishers.Optional(try? result.get().flatMap { count == 0 ? $0 : nil })
|
||||
}
|
||||
|
||||
public func drop(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? nil : $0 } })
|
||||
}
|
||||
|
||||
public func tryDrop(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? nil : $0 } }
|
||||
)
|
||||
}
|
||||
|
||||
public func first() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func last() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.map { $0.flatMap { predicate($0) ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { isIncluded($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryFilter(
|
||||
_ isIncluded: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try isIncluded($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Output, Failure> {
|
||||
return Publishers.Empty()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(result.map { $0.map(transform) })
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(result.tryMap { try $0.map(transform) })
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Publishers.Optional<Output, TransformedFailure> {
|
||||
return Publishers.Optional(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func output(at index: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(index >= 0, "index must not be negative")
|
||||
return Publishers.Optional(result.map { $0.flatMap { index == 0 ? $0 : nil } })
|
||||
}
|
||||
|
||||
public func output<RangeExpr: RangeExpression>(
|
||||
in range: RangeExpr
|
||||
) -> Publishers.Optional<Output, Failure> where RangeExpr.Bound == Int {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
let range = range.relative(to: 0..<Int.max)
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { range.lowerBound == 0 ? $0 : nil } }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually probably should be just this:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { range.contains(0) ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(_ maxLength: Int) -> Publishers.Optional<Output, Failure> {
|
||||
precondition(maxLength >= 0, "maxLength must not be negative")
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { maxLength == 0 ? $0 : nil } }
|
||||
)
|
||||
// The above implementation is used for compatibility.
|
||||
//
|
||||
// It actually should be the following:
|
||||
// return Publishers.Optional(
|
||||
// result.map { $0.flatMap { maxLength > 0 ? $0 : nil } }
|
||||
// )
|
||||
}
|
||||
|
||||
public func prefix(
|
||||
while predicate: (Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.flatMap { predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryPrefix(
|
||||
while predicate: (Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.flatMap { try predicate($0) ? $0 : nil } }
|
||||
)
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Publishers.Optional<Accumulator, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.map { nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Publishers.Optional<Accumulator, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Failure> {
|
||||
return Publishers.Optional(
|
||||
result.map { $0.map { nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Publishers.Optional<ElementOfResult, Error> {
|
||||
return Publishers.Optional(
|
||||
result.tryMap { try $0.map { try nextPartialResult(initialResult, $0) } }
|
||||
)
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Publishers.Optional<Output, Error> {
|
||||
return Publishers.Optional(result.mapError { $0 })
|
||||
}
|
||||
|
||||
public func replaceError(with output: Output) -> Publishers.Optional<Output, Never> {
|
||||
return Publishers.Optional(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(
|
||||
with output: Output
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry() -> Publishers.Optional<Output, Failure> {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Optional where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Publishers.Optional<Output, Failure> {
|
||||
return Publishers.Optional(result.success)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ extension Publishers {
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
let inner = Inner(downstream: subscriber, prefix: prefix, stream: stream)
|
||||
upstream.receive(subscriber: inner)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// Publishers.ReplaceNil.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
extension Publisher {
|
||||
|
||||
/// Replaces nil elements in the stream with the proviced element.
|
||||
///
|
||||
/// - Parameter output: The element to use when replacing `nil`.
|
||||
/// - Returns: A publisher that replaces `nil` elements from
|
||||
/// the upstream publisher with the provided element.
|
||||
public func replaceNil<ElementOfResult>(
|
||||
with output: ElementOfResult
|
||||
) -> Publishers.Map<Self, ElementOfResult>
|
||||
where Output == ElementOfResult?
|
||||
{
|
||||
return Publishers.Map(upstream: self) { $0 ?? output }
|
||||
}
|
||||
}
|
||||
@@ -110,21 +110,42 @@ extension Publishers.Sequence {
|
||||
|
||||
extension Publishers.Sequence: Equatable where Elements: Equatable {}
|
||||
|
||||
extension Publishers.Sequence where Failure == Never {
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.min(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.max(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.first(where: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.allSatisfy(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.allSatisfy(predicate) })
|
||||
}
|
||||
|
||||
public func collect() -> Publishers.Once<[Elements.Element], Failure> {
|
||||
public func collect() -> Result<[Elements.Element], Failure>.OCombine.Publisher {
|
||||
return .init(Array(sequence))
|
||||
}
|
||||
|
||||
@@ -134,39 +155,15 @@ extension Publishers.Sequence {
|
||||
return .init(sequence: sequence.compactMap(transform))
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.min(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.min(by: areInIncreasingOrder) })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.max(by: areInIncreasingOrder))
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Elements.Element, Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.max(by: areInIncreasingOrder) })
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Once<Bool, Failure> {
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.contains(where: predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Once<Bool, Error> {
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.contains(where: predicate) })
|
||||
}
|
||||
|
||||
@@ -182,26 +179,14 @@ extension Publishers.Sequence {
|
||||
return .init(sequence: sequence.dropFirst(count))
|
||||
}
|
||||
|
||||
public func first(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.first(where: predicate))
|
||||
}
|
||||
|
||||
public func tryFirst(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.first(where: predicate) })
|
||||
}
|
||||
|
||||
public func filter(
|
||||
_ isIncluded: (Elements.Element) -> Bool
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
return .init(sequence: sequence.filter(isIncluded))
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Publishers.Empty<Elements.Element, Failure> {
|
||||
return .init(completeImmediately: true)
|
||||
public func ignoreOutput() -> Empty<Elements.Element, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
@@ -225,7 +210,7 @@ extension Publishers.Sequence {
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: @escaping (Accumulator, Elements.Element) -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Failure> {
|
||||
) -> Result<Accumulator, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.reduce(initialResult, nextPartialResult))
|
||||
}
|
||||
|
||||
@@ -233,7 +218,7 @@ extension Publishers.Sequence {
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult:
|
||||
@escaping (Accumulator, Elements.Element) throws -> Accumulator
|
||||
) -> Publishers.Once<Accumulator, Error> {
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher {
|
||||
return .init(Result { try sequence.reduce(initialResult, nextPartialResult) })
|
||||
}
|
||||
|
||||
@@ -276,38 +261,43 @@ extension Publishers.Sequence where Elements.Element: Equatable {
|
||||
return .init(sequence: result)
|
||||
}
|
||||
|
||||
public func contains(_ output: Elements.Element) -> Publishers.Once<Bool, Failure> {
|
||||
public func contains(
|
||||
_ output: Elements.Element
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.contains(output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements.Element: Comparable {
|
||||
extension Publishers.Sequence where Failure == Never, Elements.Element: Comparable {
|
||||
|
||||
public func min() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func min() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.min())
|
||||
}
|
||||
|
||||
public func max() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func max() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.max())
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: Collection, Failure == Never {
|
||||
|
||||
public func first() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.first)
|
||||
}
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: Collection {
|
||||
|
||||
public func first() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.first)
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Once<Int, Failure> {
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func output(
|
||||
in range: Range<Elements.Index>
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
@@ -315,40 +305,41 @@ extension Publishers.Sequence where Elements: Collection {
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: BidirectionalCollection {
|
||||
extension Publishers.Sequence where Elements: BidirectionalCollection, Failure == Never {
|
||||
|
||||
public func last() -> Publishers.Optional<Elements.Element, Failure> {
|
||||
public func last() -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.last)
|
||||
}
|
||||
|
||||
public func last(
|
||||
where predicate: (Elements.Element) -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.last(where: predicate))
|
||||
}
|
||||
}
|
||||
|
||||
public func tryLast(
|
||||
where predicate: (Elements.Element) throws -> Bool
|
||||
) -> Publishers.Optional<Elements.Element, Error> {
|
||||
return .init(Result { try sequence.last(where: predicate) })
|
||||
extension Publishers.Sequence where Elements: RandomAccessCollection, Failure == Never {
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Optional<Elements.Element>.OCombine.Publisher {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func count() -> Just<Int> {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.Sequence where Elements: RandomAccessCollection {
|
||||
|
||||
public func output(
|
||||
at index: Elements.Index
|
||||
) -> Publishers.Optional<Elements.Element, Failure> {
|
||||
return .init(sequence.indices.contains(index) ? sequence[index] : nil)
|
||||
}
|
||||
|
||||
public func output(
|
||||
in range: Range<Elements.Index>
|
||||
) -> Publishers.Sequence<[Elements.Element], Failure> {
|
||||
return .init(sequence: Array(sequence[range]))
|
||||
}
|
||||
|
||||
public func count() -> Publishers.Optional<Int, Failure> {
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(sequence.count)
|
||||
}
|
||||
}
|
||||
@@ -408,7 +399,7 @@ extension Publishers.Sequence where Elements: RangeReplaceableCollection {
|
||||
|
||||
extension Sequence {
|
||||
|
||||
public func publisher() -> Publishers.Sequence<Self, Never> {
|
||||
return Publishers.Sequence(sequence: self)
|
||||
public var publisher: Publishers.Sequence<Self, Never> {
|
||||
return .init(sequence: self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Publishers.SetFailureType.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.07.2019.
|
||||
//
|
||||
|
||||
extension Publishers {
|
||||
|
||||
/// A publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// The publisher cannot actually fail with the specified type and instead
|
||||
/// just finishes normally. Use this publisher type when you need to match
|
||||
/// the error types for two mismatched publishers.
|
||||
public struct SetFailureType<Upstream: Publisher, Failure: Error>: Publisher
|
||||
where Upstream.Failure == Never
|
||||
{
|
||||
public typealias Output = Upstream.Output
|
||||
|
||||
/// The publisher from which this publisher receives elements.
|
||||
public let upstream: Upstream
|
||||
|
||||
/// Creates a publisher that appears to send a specified failure type.
|
||||
///
|
||||
/// - Parameter upstream: The publisher from which this publisher receives
|
||||
/// elements.
|
||||
public init(upstream: Upstream) {
|
||||
self.upstream = upstream
|
||||
}
|
||||
|
||||
public func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == Failure, Downstream.Input == Output
|
||||
{
|
||||
let inner = Inner(downstream: subscriber)
|
||||
upstream.subscribe(inner)
|
||||
}
|
||||
|
||||
public func setFailureType<NewFailure: Error>(
|
||||
to failure: NewFailure.Type
|
||||
) -> Publishers.SetFailureType<Upstream, NewFailure> {
|
||||
return .init(upstream: upstream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType: Equatable where Upstream: Equatable {}
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Changes the failure type declared by the upstream publisher.
|
||||
///
|
||||
/// The publisher returned by this method cannot actually fail
|
||||
/// with the specified type and instead just finishes normally. Instead, you use
|
||||
/// this method when you need to match the error types of two mismatched publishers.
|
||||
///
|
||||
/// - Parameter failureType: The `Failure` type presented by this publisher.
|
||||
/// - Returns: A publisher that appears to send the specified failure type.
|
||||
public func setFailureType<NewFailure: Error>(
|
||||
to failureType: NewFailure.Type
|
||||
) -> Publishers.SetFailureType<Self, NewFailure> {
|
||||
return .init(upstream: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publishers.SetFailureType {
|
||||
private final class Inner<Downstream: Subscriber>
|
||||
: OperatorSubscription<Downstream>,
|
||||
Subscriber,
|
||||
CustomStringConvertible
|
||||
where Upstream.Output == Downstream.Input
|
||||
{
|
||||
func receive(subscription: Subscription) {
|
||||
downstream.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func receive(_ input: Upstream.Output) -> Subscribers.Demand {
|
||||
return downstream.receive(input)
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Never>) {
|
||||
downstream.receive(completion: .finished)
|
||||
}
|
||||
|
||||
var description: String { return "SetFailureType" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
//
|
||||
// Result.Publisher.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
//
|
||||
|
||||
extension Result {
|
||||
|
||||
/// A namespace for disambiguation when both OpenCombine and Combine are imported.
|
||||
///
|
||||
/// Combine extends `Result` with a nested type `Publisher`.
|
||||
/// If you import both OpenCombine and Combine (either explicitly or implicitly,
|
||||
/// e. g. when importing Foundation), you will not be able
|
||||
/// to write `Result<Int, Error>.Publisher`,
|
||||
/// because Swift is unable to understand which `Publisher` you're referring to.
|
||||
///
|
||||
/// So you have to write `Result<Int, Error>.OCombine.Publisher`.
|
||||
///
|
||||
/// This bug is tracked [here](https://bugs.swift.org/browse/SR-11183).
|
||||
///
|
||||
/// You can omit this whenever Combine is not available (e. g. on Linux).
|
||||
public struct OCombine {
|
||||
|
||||
fileprivate let result: Result
|
||||
|
||||
fileprivate init(_ result: Result) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(result)
|
||||
}
|
||||
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`,
|
||||
/// then `Once` sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error
|
||||
/// instead of sending a value. In contrast with `Optional`, a `Once` publisher
|
||||
/// always sends one value (unless it terminates with an error).
|
||||
public struct Publisher: OpenCombine.Publisher {
|
||||
|
||||
public typealias Output = Success
|
||||
|
||||
/// The result to deliver to each subscriber.
|
||||
public let result: Result
|
||||
|
||||
/// Creates a publisher that delivers the specified result.
|
||||
///
|
||||
/// If the result is `.success`, the `Once` publisher sends the specified
|
||||
/// output to all subscribers and finishes normally. If the result is
|
||||
/// `.failure`, then the publisher fails immediately with the specified
|
||||
/// error.
|
||||
///
|
||||
/// - Parameter result: The result to deliver to each subscriber.
|
||||
public init(_ result: Result) {
|
||||
self.result = result
|
||||
}
|
||||
|
||||
/// Creates a publisher that sends the specified output to all subscribers and
|
||||
/// finishes normally.
|
||||
///
|
||||
/// - Parameter output: The output to deliver to each subscriber.
|
||||
public init(_ output: Output) {
|
||||
self.init(.success(output))
|
||||
}
|
||||
|
||||
/// Creates a publisher that immediately terminates upon subscription with
|
||||
/// the given failure.
|
||||
///
|
||||
/// - Parameter failure: The failure to send when terminating.
|
||||
public init(_ failure: Failure) {
|
||||
self.init(.failure(failure))
|
||||
}
|
||||
|
||||
public func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where SubscriberType.Input == Success, SubscriberType.Failure == Failure
|
||||
{
|
||||
switch result {
|
||||
case .success(let value):
|
||||
subscriber.receive(subscription: Inner(value: value,
|
||||
downstream: subscriber))
|
||||
case .failure(let failure):
|
||||
subscriber.receive(subscription: Subscriptions.empty)
|
||||
subscriber.receive(completion: .failure(failure))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var ocombine: OCombine {
|
||||
return OCombine(self)
|
||||
}
|
||||
|
||||
#if !canImport(Combine)
|
||||
/// A publisher that publishes an output to each subscriber exactly once then
|
||||
/// finishes, or fails immediately without producing any elements.
|
||||
///
|
||||
/// If `result` is `.success`, then `Once` waits until it receives a request for
|
||||
/// at least 1 value before sending the output. If `result` is `.failure`, then `Once`
|
||||
/// sends the failure immediately upon subscription.
|
||||
///
|
||||
/// In contrast with `Just`, a `Once` publisher can terminate with an error instead of
|
||||
/// sending a value. In contrast with `Optional`, a `Once` publisher always sends one
|
||||
/// value (unless it terminates with an error).
|
||||
public typealias Publisher = OCombine.Publisher
|
||||
|
||||
public var publisher: Publisher {
|
||||
return Publisher(self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class Inner<SubscriberType: Subscriber>: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private let _output: SubscriberType.Input
|
||||
private var _downstream: SubscriberType?
|
||||
|
||||
init(value: SubscriberType.Input, downstream: SubscriberType) {
|
||||
_output = value
|
||||
_downstream = downstream
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
if let downstream = _downstream, demand > 0 {
|
||||
_ = downstream.receive(_output)
|
||||
downstream.receive(completion: .finished)
|
||||
_downstream = nil
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
_downstream = nil
|
||||
}
|
||||
|
||||
var description: String { return "Once" }
|
||||
|
||||
var customMirror: Mirror {
|
||||
return Mirror(self, unlabeledChildren: CollectionOfOne(_output))
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher: Equatable
|
||||
where Output: Equatable, Failure: Equatable
|
||||
{
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Output: Equatable {
|
||||
|
||||
public func contains(_ output: Output) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { $0 == output })
|
||||
}
|
||||
|
||||
public func removeDuplicates() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Output: Comparable {
|
||||
|
||||
public func min() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func max() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher {
|
||||
|
||||
public func allSatisfy(
|
||||
_ predicate: (Output) -> Bool
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryAllSatisfy(
|
||||
_ predicate: (Output) throws -> Bool
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func contains(
|
||||
where predicate: (Output) -> Bool
|
||||
) -> Result<Bool, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(predicate))
|
||||
}
|
||||
|
||||
public func tryContains(
|
||||
where predicate: (Output) throws -> Bool
|
||||
) -> Result<Bool, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(predicate))
|
||||
}
|
||||
|
||||
public func collect() -> Result<[Output], Failure>.OCombine.Publisher {
|
||||
return .init(result.map { [$0] })
|
||||
}
|
||||
|
||||
public func min(
|
||||
by areInIncreasingOrder: (Output, Output) -> Bool
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMin(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func max(
|
||||
by areInIncreasingOrder: (Output, Output
|
||||
) -> Bool) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryMax(
|
||||
by areInIncreasingOrder: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try areInIncreasingOrder($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func count() -> Result<Int, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { _ in 1 })
|
||||
}
|
||||
|
||||
public func first() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func last() -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func ignoreOutput() -> Empty<Output, Failure> {
|
||||
return .init()
|
||||
}
|
||||
|
||||
public func map<ElementOfResult>(
|
||||
_ transform: (Output) -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
|
||||
return .init(result.map(transform))
|
||||
}
|
||||
|
||||
public func tryMap<ElementOfResult>(
|
||||
_ transform: (Output) throws -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap(transform))
|
||||
}
|
||||
|
||||
public func mapError<TransformedFailure: Error>(
|
||||
_ transform: (Failure) -> TransformedFailure
|
||||
) -> Result<Output, TransformedFailure>.OCombine.Publisher {
|
||||
return .init(result.mapError(transform))
|
||||
}
|
||||
|
||||
public func removeDuplicates(
|
||||
by predicate: (Output, Output) -> Bool
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func tryRemoveDuplicates(
|
||||
by predicate: (Output, Output) throws -> Bool
|
||||
) -> Result<Output, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { _ = try predicate($0, $0); return $0 })
|
||||
}
|
||||
|
||||
public func replaceError(
|
||||
with output: Output
|
||||
) -> Result<Output, Never>.OCombine.Publisher {
|
||||
return .init(.success(result.unwrapOr(output)))
|
||||
}
|
||||
|
||||
public func replaceEmpty(
|
||||
with output: Output
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func retry(_ times: Int) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return self
|
||||
}
|
||||
|
||||
public func reduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) -> Accumulator
|
||||
) -> Result<Accumulator, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryReduce<Accumulator>(
|
||||
_ initialResult: Accumulator,
|
||||
_ nextPartialResult: (Accumulator, Output) throws -> Accumulator
|
||||
) -> Result<Accumulator, Error>.OCombine.Publisher{
|
||||
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func scan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Failure>.OCombine.Publisher {
|
||||
return .init(result.map { nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
|
||||
public func tryScan<ElementOfResult>(
|
||||
_ initialResult: ElementOfResult,
|
||||
_ nextPartialResult: (ElementOfResult, Output) throws -> ElementOfResult
|
||||
) -> Result<ElementOfResult, Error>.OCombine.Publisher {
|
||||
return .init(result.tryMap { try nextPartialResult(initialResult, $0) })
|
||||
}
|
||||
}
|
||||
|
||||
extension Result.OCombine.Publisher where Failure == Never {
|
||||
|
||||
public func setFailureType<Failure: Error>(
|
||||
to failureType: Failure.Type
|
||||
) -> Result<Output, Failure>.OCombine.Publisher {
|
||||
return .init(result.success)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@ public protocol Subject: AnyObject, Publisher {
|
||||
/// - Parameter completion: A `Completion` instance which indicates whether publishing
|
||||
/// has finished normally or failed with an error.
|
||||
func send(completion: Subscribers.Completion<Failure>)
|
||||
|
||||
/// Provides this Subject an opportunity to establish demand for any new upstream
|
||||
/// subscriptions (say, via `Publisher.subscribe<S: Subject>(_: Subject)`)
|
||||
func send(subscription: Subscription)
|
||||
}
|
||||
|
||||
extension Subject where Output == Void {
|
||||
|
||||
@@ -45,3 +45,18 @@ extension Subscriber where Input == Void {
|
||||
return receive(())
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional where Wrapped: Subscriber {
|
||||
|
||||
internal func receive(subscription: Subscription) {
|
||||
self?.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
internal func receive(_ input: Wrapped.Input) -> Subscribers.Demand {
|
||||
return self?.receive(input) ?? .none
|
||||
}
|
||||
|
||||
internal func receive(completion: Subscribers.Completion<Wrapped.Failure>) {
|
||||
self?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,13 +69,14 @@ extension Subscribers {
|
||||
|
||||
extension Publisher where Self.Failure == Never {
|
||||
|
||||
/// Assigns the value of a KVO-compliant property from a publisher.
|
||||
/// Assigns each element from a Publisher to a property on an object.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyPath: The key path of the property to assign.
|
||||
/// - object: The object on which to assign the value.
|
||||
/// - Returns: A cancellable instance; used when you end KVO-based assignment of
|
||||
/// the key path’s value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment
|
||||
/// of the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Output>,
|
||||
on object: Root) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Assign(object: object, keyPath: keyPath)
|
||||
|
||||
@@ -24,23 +24,14 @@ extension Subscribers.Completion: Equatable where Failure: Equatable {}
|
||||
|
||||
extension Subscribers.Completion: Hashable where Failure: Hashable {}
|
||||
|
||||
extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Encodable {
|
||||
|
||||
extension Subscribers.Completion {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case success = "success"
|
||||
case error = "error"
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let success = try container.decode(Bool.self, forKey: .success)
|
||||
if success {
|
||||
self = .finished
|
||||
} else {
|
||||
let error = try container.decode(Failure.self, forKey: .error)
|
||||
self = .failure(error)
|
||||
}
|
||||
}
|
||||
extension Subscribers.Completion: Encodable where Failure: Encodable {
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
@@ -54,6 +45,19 @@ extension Subscribers.Completion: Codable where Failure: Decodable, Failure: Enc
|
||||
}
|
||||
}
|
||||
|
||||
extension Subscribers.Completion: Decodable where Failure: Decodable {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let success = try container.decode(Bool.self, forKey: .success)
|
||||
if success {
|
||||
self = .finished
|
||||
} else {
|
||||
let error = try container.decode(Failure.self, forKey: .error)
|
||||
self = .failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Subscribers.Completion {
|
||||
|
||||
/// Erases the `Failure` type to `Swift.Error`. This function exists
|
||||
|
||||
@@ -20,28 +20,31 @@ extension Subscribers {
|
||||
Codable,
|
||||
CustomStringConvertible
|
||||
{
|
||||
private var rawValue: UInt
|
||||
@usableFromInline
|
||||
internal let rawValue: UInt
|
||||
|
||||
private static let _rawValueUnlimited = UInt(Int.max) + 1
|
||||
|
||||
private init<Integer: BinaryInteger>(_ rawValue: Integer) {
|
||||
if rawValue < 0 {
|
||||
self.rawValue = 0
|
||||
} else if rawValue > Demand._rawValueUnlimited {
|
||||
self.rawValue = Demand._rawValueUnlimited
|
||||
} else {
|
||||
self.rawValue = UInt(rawValue)
|
||||
}
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
internal init(rawValue: UInt) {
|
||||
self.rawValue = min(UInt(Int.max) + 1, rawValue)
|
||||
}
|
||||
|
||||
/// Requests as many values as the `Publisher` can produce.
|
||||
public static let unlimited = Subscribers.Demand(_rawValueUnlimited)
|
||||
public static let unlimited = Demand(rawValue: .max)
|
||||
|
||||
/// A demand for no items.
|
||||
///
|
||||
/// This is equivalent to `Demand.max(0)`.
|
||||
public static let none = Demand.max(0)
|
||||
|
||||
/// Limits the maximum number of values.
|
||||
/// The `Publisher` may send fewer than the requested number.
|
||||
/// Negative values will result in a `fatalError`.
|
||||
public static func max(_ value: Int) -> Subscribers.Demand {
|
||||
return .init(UInt(value))
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func max(_ value: Int) -> Demand {
|
||||
precondition(value >= 0, "demand cannot be negative")
|
||||
return Demand(rawValue: UInt(value))
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
@@ -53,6 +56,8 @@ extension Subscribers {
|
||||
}
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func + (lhs: Demand, rhs: Demand) -> Demand {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
@@ -60,31 +65,34 @@ extension Subscribers {
|
||||
case (_, .unlimited):
|
||||
return .unlimited
|
||||
default:
|
||||
let (sum, isOverflow) = lhs.rawValue.addingReportingOverflow(rhs.rawValue)
|
||||
return isOverflow ? .unlimited : .init(sum)
|
||||
let (sum, isOverflow) = Int(lhs.rawValue)
|
||||
.addingReportingOverflow(Int(rhs.rawValue))
|
||||
return isOverflow ? .unlimited : .max(sum)
|
||||
}
|
||||
}
|
||||
|
||||
/// A demand for no items.
|
||||
///
|
||||
/// This is equivalent to `Demand.max(0)`.
|
||||
public static let none: Demand = .max(0)
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func += (lhs: inout Demand, rhs: Demand) {
|
||||
if lhs == .unlimited { return }
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
|
||||
/// When adding any value to` .unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func + (lhs: Demand, rhs: Int) -> Demand {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
return Demand(lhs.rawValue.advanced(by: rhs))
|
||||
let (sum, isOverflow) = Int(lhs.rawValue).addingReportingOverflow(rhs)
|
||||
return isOverflow ? .unlimited : .max(sum)
|
||||
}
|
||||
|
||||
/// When adding any value to `.unlimited`, the result is `.unlimited`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func += (lhs: inout Demand, rhs: Int) {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
@@ -93,21 +101,23 @@ extension Subscribers {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
let (product, isOverflow) =
|
||||
lhs.rawValue.multipliedReportingOverflow(by: UInt(rhs))
|
||||
|
||||
return isOverflow ? .unlimited : .init(product)
|
||||
let (product, isOverflow) = Int(lhs.rawValue)
|
||||
.multipliedReportingOverflow(by: rhs)
|
||||
return isOverflow ? .unlimited : .max(product)
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func *= (lhs: inout Demand, rhs: Int) {
|
||||
lhs = lhs * rhs
|
||||
}
|
||||
|
||||
/// When subtracting any value (including .unlimited) from .unlimited,
|
||||
/// the result is still .unlimited. Subtracting unlimited from any value
|
||||
/// (except unlimited) results in .max(0). A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to .max(0).
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting `.unlimited` from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to `.max(0)`.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func - (lhs: Demand, rhs: Demand) -> Demand {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
@@ -115,110 +125,231 @@ extension Subscribers {
|
||||
case (_, .unlimited):
|
||||
return .none
|
||||
default:
|
||||
let (difference, isOverflow) =
|
||||
lhs.rawValue.subtractingReportingOverflow(rhs.rawValue)
|
||||
return isOverflow ? .none : .init(difference)
|
||||
let (difference, isOverflow) = Int(lhs.rawValue)
|
||||
.subtractingReportingOverflow(Int(rhs.rawValue))
|
||||
return isOverflow ? .none : .max(difference)
|
||||
}
|
||||
}
|
||||
|
||||
/// When subtracting any value (including .unlimited) from .unlimited,
|
||||
/// the result is still .unlimited. Subtracting unlimited from any value
|
||||
/// (except unlimited) results in .max(0). A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to .max(0).
|
||||
/// When subtracting any value (including `.unlimited`) from `.unlimited`,
|
||||
/// the result is still `.unlimited`. Subtracting unlimited from any value
|
||||
/// (except `.unlimited`) results in `.max(0)`. A negative demand is not possible;
|
||||
/// any operation that would result in a negative value is clamped to `.max(0)`.
|
||||
/// but be aware that it is not usable when requesting values in a subscription.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func -= (lhs: inout Demand, rhs: Demand) {
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
/// When subtracting any value from .unlimited, the result is still .unlimited.
|
||||
/// When subtracting any value from `.unlimited`, the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
/// a negative value is clamped to .max(0)
|
||||
/// a negative value is clamped to `.max(0)`
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func - (lhs: Demand, rhs: Int) -> Demand {
|
||||
if lhs == .unlimited {
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
let (difference, isOverflow) =
|
||||
Int(lhs.rawValue).subtractingReportingOverflow(rhs)
|
||||
return isOverflow ? .none : .init(difference)
|
||||
let (difference, isOverflow) = Int(lhs.rawValue)
|
||||
.subtractingReportingOverflow(rhs)
|
||||
return isOverflow ? .none : .max(difference)
|
||||
}
|
||||
|
||||
/// When subtracting any value from .unlimited, the result is still .unlimited.
|
||||
/// When subtracting any value from `.unlimited,` the result is still
|
||||
/// `.unlimited`.
|
||||
/// A negative demand is not possible; any operation that would result in
|
||||
/// a negative value is clamped to .max(0)
|
||||
/// a negative value is clamped to `.max(0)`
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func -= (lhs: inout Demand, rhs: Int) {
|
||||
if lhs == .unlimited { return }
|
||||
lhs = lhs - rhs
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs > .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) > rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs >= .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) >= rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) > rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return lhs > Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) >= rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return lhs >= Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs < .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) < rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) < rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return lhs < Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs <= .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) <= rhs
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) <= rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return lhs <= Int(rhs.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// If `lhs` is `.unlimited`, then the result is always `false`.
|
||||
/// If `rhs` is `.unlimited` then the result is `false` iff `lhs` is `.unlimited`
|
||||
/// Otherwise, the two `.max` values are compared.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func < (lhs: Demand, rhs: Demand) -> Bool {
|
||||
if lhs == .unlimited {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, _):
|
||||
return false
|
||||
}
|
||||
|
||||
if rhs == .unlimited {
|
||||
case (_, .unlimited):
|
||||
return true
|
||||
default:
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func <= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return true
|
||||
case (.unlimited, _):
|
||||
return false
|
||||
case (_, .unlimited):
|
||||
return true
|
||||
default:
|
||||
return lhs.rawValue <= rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func >= (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return true
|
||||
case (.unlimited, _):
|
||||
return true
|
||||
case (_, .unlimited):
|
||||
return false
|
||||
default:
|
||||
return lhs.rawValue >= rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func > (lhs: Demand, rhs: Demand) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.unlimited, .unlimited):
|
||||
return false
|
||||
case (.unlimited, _):
|
||||
return true
|
||||
case (_, .unlimited):
|
||||
return false
|
||||
default:
|
||||
return lhs.rawValue > rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
@inline(__always)
|
||||
@inlinable
|
||||
public static func == (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs == .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return Int(lhs.rawValue) == rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
public static func != (lhs: Demand, rhs: Int) -> Bool {
|
||||
return lhs != .max(rhs)
|
||||
if lhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(lhs.rawValue) != rhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are equal. `.unlimited` is not equal to any
|
||||
/// integer.
|
||||
public static func == (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) == rhs
|
||||
if rhs == .unlimited {
|
||||
return false
|
||||
} else {
|
||||
return rhs.rawValue == lhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `lhs` and `rhs` are not equal. `.unlimited` is not equal to
|
||||
/// any integer.
|
||||
public static func != (lhs: Int, rhs: Demand) -> Bool {
|
||||
return .max(lhs) != rhs
|
||||
if rhs == .unlimited {
|
||||
return true
|
||||
} else {
|
||||
return Int(rhs.rawValue) != lhs
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of requested values, or `nil` if `.unlimited`.
|
||||
@@ -229,5 +360,14 @@ extension Subscribers {
|
||||
return Int(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
try self.init(rawValue: decoder.singleValueContainer().decode(UInt.self))
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ extension Subscribers {
|
||||
/// Initializes a sink with the provided closures.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - receiveValue: The closure to execute on receipt of a value. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - receiveCompletion: The closure to execute on completion. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
public init(receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveValue: @escaping ((Input) -> Void)) {
|
||||
self.receiveCompletion = receiveCompletion ?? { _ in }
|
||||
/// - receiveCompletion: The closure to execute on completion.
|
||||
/// - receiveValue: The closure to execute on receipt of a value.
|
||||
public init(
|
||||
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
|
||||
receiveValue: @escaping ((Input) -> Void)
|
||||
) {
|
||||
self.receiveCompletion = receiveCompletion
|
||||
self.receiveValue = receiveValue
|
||||
}
|
||||
|
||||
@@ -75,21 +75,44 @@ extension Publisher {
|
||||
///
|
||||
/// This method creates the subscriber and immediately requests an unlimited number
|
||||
/// of values, prior to returning the subscriber.
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - parameter receiveComplete: The closure to execute on completion. If `nil`,
|
||||
/// the sink uses an empty closure.
|
||||
/// - Returns: A subscriber that performs the provided closures upon receiving values
|
||||
/// or completion.
|
||||
///
|
||||
/// - parameter receiveComplete: The closure to execute on completion.
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment of
|
||||
/// the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func sink(
|
||||
receiveCompletion: ((Subscribers.Completion<Failure>) -> Void)? = nil,
|
||||
receiveCompletion: @escaping (Subscribers.Completion<Failure>) -> Void,
|
||||
receiveValue: @escaping ((Output) -> Void)
|
||||
) -> Subscribers.Sink<Output, Failure> {
|
||||
) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Sink<Output, Failure>(
|
||||
receiveCompletion: receiveCompletion,
|
||||
receiveValue: receiveValue
|
||||
)
|
||||
subscribe(subscriber)
|
||||
return subscriber
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
extension Publisher where Failure == Never {
|
||||
|
||||
/// Attaches a subscriber with closure-based behavior.
|
||||
///
|
||||
/// This method creates the subscriber and immediately requests an unlimited number
|
||||
/// of values, prior to returning the subscriber.
|
||||
///
|
||||
/// - parameter receiveValue: The closure to execute on receipt of a value.
|
||||
/// - Returns: A cancellable instance; used when you end assignment of
|
||||
/// the received value. Deallocation of the result will tear down
|
||||
/// the subscription stream.
|
||||
public func sink(
|
||||
receiveValue: @escaping (Output) -> Void
|
||||
) -> AnyCancellable {
|
||||
let subscriber = Subscribers.Sink<Output, Failure>(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: receiveValue
|
||||
)
|
||||
subscribe(subscriber)
|
||||
return AnyCancellable(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,20 +13,20 @@ extension Subscriptions {
|
||||
///
|
||||
/// Use the empty subscription when you need a `Subscription` that ignores requests
|
||||
/// and cancellation.
|
||||
public static var empty: Subscription { return Empty.shared }
|
||||
public static var empty: Subscription { return EmptySubscription.shared }
|
||||
}
|
||||
|
||||
private final class Empty: Subscription, CustomStringConvertible, CustomReflectable {
|
||||
|
||||
private final class EmptySubscription: Subscription,
|
||||
CustomStringConvertible,
|
||||
CustomReflectable
|
||||
{
|
||||
private init() {}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {}
|
||||
|
||||
func cancel() {}
|
||||
|
||||
var combineIdentifier: CombineIdentifier { return CombineIdentifier() }
|
||||
|
||||
static let shared = Empty()
|
||||
fileprivate static let shared = EmptySubscription()
|
||||
|
||||
var description: String { return "Empty" }
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ import OpenCombineTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += OpenCombineTests.allTests()
|
||||
|
||||
XCTMain(tests)
|
||||
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnyCancellableTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -21,6 +21,8 @@ final class AnyCancellableTests: XCTestCase {
|
||||
("testCancelableInitialized", testCancelableInitialized),
|
||||
("testCancelTwice", testCancelTwice),
|
||||
("testStoreInArbitraryCollection", testStoreInArbitraryCollection),
|
||||
("testStoreInSet", testStoreInSet),
|
||||
("testIndirectCancellation", testIndirectCancellation),
|
||||
]
|
||||
|
||||
func testClosureInitialized() {
|
||||
@@ -97,11 +99,56 @@ final class AnyCancellableTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append])
|
||||
|
||||
let cancellable2 = AnyCancellable({})
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
cancellable2.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append])
|
||||
|
||||
XCTAssertEqual(disposeBag.storage, [cancellable1, cancellable2])
|
||||
|
||||
let cancellable2Abstracted: Cancellable = cancellable2
|
||||
cancellable2Abstracted.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.history, [.emptyInit, .append, .append, .append])
|
||||
XCTAssertEqual(disposeBag.storage.count, 3)
|
||||
|
||||
if disposeBag.storage.count == 3 {
|
||||
XCTAssertNotEqual(disposeBag.storage[2], cancellable2)
|
||||
}
|
||||
}
|
||||
|
||||
func testStoreInSet() {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let cancellable1 = AnyCancellable({})
|
||||
cancellable1.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag, [cancellable1])
|
||||
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
cancellable2.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
|
||||
|
||||
cancellable2.store(in: &disposeBag)
|
||||
XCTAssertEqual(disposeBag, [cancellable1, cancellable2])
|
||||
|
||||
let cancellable2Abstracted: Cancellable = cancellable2
|
||||
cancellable2Abstracted.store(in: &disposeBag)
|
||||
|
||||
XCTAssertEqual(disposeBag.count, 3)
|
||||
}
|
||||
|
||||
func testIndirectCancellation() {
|
||||
let subscription = CustomSubscription()
|
||||
let cancellable1 = AnyCancellable(subscription)
|
||||
let cancellable2 = AnyCancellable(cancellable1)
|
||||
XCTAssert(subscription.history.isEmpty)
|
||||
|
||||
cancellable2.cancel()
|
||||
XCTAssertEqual(subscription.history, [.cancelled])
|
||||
|
||||
cancellable1.cancel()
|
||||
XCTAssertEqual(subscription.history, [.cancelled])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,22 +13,33 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnyPublisherTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testErasePublisher", testErasePublisher),
|
||||
("testDescription", testDescription),
|
||||
]
|
||||
|
||||
private typealias Sut = AnyPublisher<Int, TestingError>
|
||||
|
||||
func testErasePublisher() {
|
||||
|
||||
let publisher = TrackingSubject<Int>()
|
||||
let erased = AnyPublisher(publisher)
|
||||
let subscriber = TrackingSubscriber()
|
||||
let publisher = TrackingSubject<Int>(
|
||||
receiveSubscriber: {
|
||||
XCTAssertEqual($0.combineIdentifier, subscriber.combineIdentifier)
|
||||
}
|
||||
)
|
||||
let erased = AnyPublisher(publisher)
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
XCTAssertEqual(publisher.history, [.subscriber(subscriber.combineIdentifier)])
|
||||
erased.subscribe(subscriber)
|
||||
XCTAssertEqual(publisher.history, [.subscriber])
|
||||
}
|
||||
|
||||
func testDescription() {
|
||||
let erased = AnyPublisher(TrackingSubject<Int>())
|
||||
XCTAssertEqual(erased.description, "AnyPublisher")
|
||||
XCTAssertEqual(erased.description, erased.playgroundDescription as? String)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// AnySubjectTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 16.06.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class AnySubjectTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testEraseSubject", testEraseSubject),
|
||||
("testClosureBasedSubject", testClosureBasedSubject),
|
||||
]
|
||||
|
||||
private typealias Sut = AnyPublisher<Int, TestingError>
|
||||
|
||||
func testEraseSubject() {
|
||||
|
||||
let subject = TrackingSubject<Int>()
|
||||
let erased = AnySubject(subject)
|
||||
let subscriber = TrackingSubscriber()
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
erased.send(42)
|
||||
erased.send(completion: .finished)
|
||||
erased.send(completion: .failure("f"))
|
||||
erased.send(12)
|
||||
erased.receive(subscriber: subscriber)
|
||||
|
||||
XCTAssertEqual(subject.history, [.subscriber(subscriber.combineIdentifier),
|
||||
.value(42),
|
||||
.completion(.finished),
|
||||
.completion(.failure("f")),
|
||||
.value(12),
|
||||
.subscriber(subscriber.combineIdentifier)])
|
||||
}
|
||||
|
||||
func testClosureBasedSubject() {
|
||||
|
||||
var events: [TrackingSubject<Int>.Event] = []
|
||||
|
||||
let erased = AnySubject<Int, TestingError>(
|
||||
{ events.append(.subscriber($0.combineIdentifier)) },
|
||||
{ events.append(.value($0)) },
|
||||
{ events.append(.completion($0)) }
|
||||
)
|
||||
let subscriber = TrackingSubscriber()
|
||||
|
||||
erased.receive(subscriber: subscriber)
|
||||
erased.send(42)
|
||||
erased.send(completion: .finished)
|
||||
erased.send(completion: .failure("f"))
|
||||
erased.send(12)
|
||||
erased.receive(subscriber: subscriber)
|
||||
|
||||
XCTAssertEqual(events, [.subscriber(subscriber.combineIdentifier),
|
||||
.value(42),
|
||||
.completion(.finished),
|
||||
.completion(.failure("f")),
|
||||
.value(12),
|
||||
.subscriber(subscriber.combineIdentifier)])
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private typealias Sut = AnySubscriber<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -38,18 +38,28 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
let subscriber1 = TrackingSubscriber()
|
||||
let subscriber2 = TrackingSubscriber()
|
||||
XCTAssertNotEqual(Sut(subscriber1).combineIdentifier,
|
||||
Sut(subscriber2).combineIdentifier)
|
||||
XCTAssertEqual(subscriber1.combineIdentifier,
|
||||
Sut(subscriber1).combineIdentifier)
|
||||
XCTAssertEqual(subscriber2.combineIdentifier,
|
||||
Sut(subscriber2).combineIdentifier)
|
||||
|
||||
do {
|
||||
let erased1 = Sut(subscriber1)
|
||||
let erased2 = Sut(subscriber2)
|
||||
XCTAssertNotEqual(erased1.combineIdentifier, erased2.combineIdentifier)
|
||||
}
|
||||
|
||||
do {
|
||||
let subject = Sut(subscriber1)
|
||||
XCTAssertEqual(subscriber1.combineIdentifier, subject.combineIdentifier)
|
||||
}
|
||||
|
||||
do {
|
||||
let subject = Sut(subscriber2)
|
||||
XCTAssertEqual(subscriber2.combineIdentifier, subject.combineIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
func testDescription() {
|
||||
|
||||
let empty = Sut()
|
||||
XCTAssertEqual(empty.description, "AnySubscriber")
|
||||
XCTAssertEqual(empty.description, "Anonymous AnySubscriber")
|
||||
XCTAssertEqual(empty.description, empty.playgroundDescription as? String)
|
||||
|
||||
let subject = PassthroughSubject<Int, TestingError>()
|
||||
@@ -71,7 +81,7 @@ final class AnySubscriberTests: XCTestCase {
|
||||
let empty = Sut()
|
||||
XCTAssertEqual(
|
||||
String(describing: Mirror(reflecting: empty).subjectType),
|
||||
"CombineIdentifier"
|
||||
"String"
|
||||
)
|
||||
XCTAssert(Mirror(reflecting: empty).children.isEmpty)
|
||||
|
||||
@@ -123,7 +133,7 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, publisher)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
@@ -136,8 +146,8 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, erased)
|
||||
|
||||
let expectedEvents: [TrackingSubject.Event] =
|
||||
events.compactMap(subscriberEventToSubjectEvent)
|
||||
let expectedEvents: [TrackingSubject<Int>.Event] =
|
||||
[.subscription("Subject")] + events.compactMap(subscriberEventToSubjectEvent)
|
||||
|
||||
XCTAssertEqual(subject.history, expectedEvents)
|
||||
|
||||
@@ -167,19 +177,16 @@ final class AnySubscriberTests: XCTestCase {
|
||||
|
||||
publishEvents(events, publisher)
|
||||
|
||||
XCTAssertEqual(subject.history, [.value(31),
|
||||
.value(42),
|
||||
.value(-1),
|
||||
.value(141241241),
|
||||
XCTAssertEqual(subject.history, [.subscription("Subject"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private let events: [TrackingSubscriber.Event] = [
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription("1"),
|
||||
.subscription("2"),
|
||||
.subscription("3"),
|
||||
.value(31),
|
||||
.value(42),
|
||||
.value(-1),
|
||||
@@ -189,7 +196,7 @@ private let events: [TrackingSubscriber.Event] = [
|
||||
.completion(.failure("failure"))
|
||||
]
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut) {
|
||||
for event in events {
|
||||
switch event {
|
||||
@@ -203,15 +210,15 @@ private func publishEvents(_ events: [TrackingSubscriber.Event], _ erased: Sut)
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func publishEvents(
|
||||
_ events: [TrackingSubscriber.Event],
|
||||
_ publisher: PassthroughSubject<Int, TestingError>
|
||||
) {
|
||||
for event in events {
|
||||
switch event {
|
||||
case .subscription:
|
||||
break
|
||||
case .subscription(let s):
|
||||
publisher.send(subscription: s)
|
||||
case .value(let v):
|
||||
publisher.send(v)
|
||||
case .completion(let c):
|
||||
@@ -220,7 +227,7 @@ private func publishEvents(
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func subscriberEventToSubjectEvent(
|
||||
_ from: TrackingSubscriber.Event
|
||||
) -> TrackingSubject<Int>.Event? {
|
||||
|
||||
@@ -14,7 +14,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CombineIdentifierTests: PerformanceTestCase {
|
||||
|
||||
static let allTests = [
|
||||
|
||||
@@ -15,15 +15,18 @@ import OpenCombine
|
||||
|
||||
// swiftlint:disable explicit_top_level_acl
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testRequestingDemand", testRequestingDemand),
|
||||
("testCrashOnZeroInitialDemand", testCrashOnZeroInitialDemand),
|
||||
("testSendFailureCompletion", testSendFailureCompletion),
|
||||
("testMultipleSubscriptions", testMultipleSubscriptions),
|
||||
("testMultipleCompletions", testMultipleCompletions),
|
||||
("testValuesAfterCompletion", testValuesAfterCompletion),
|
||||
("testSubscriptionAfterCompletion", testSubscriptionAfterCompletion),
|
||||
("testSendSubscription", testSendSubscription),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testSynchronization", testSynchronization),
|
||||
]
|
||||
@@ -114,6 +117,16 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
|
||||
}
|
||||
|
||||
func testCrashOnZeroInitialDemand() {
|
||||
assertCrashes {
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.none) }
|
||||
)
|
||||
|
||||
Sut(1).subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
func testSendFailureCompletion() {
|
||||
let cvs = Sut(0)
|
||||
let subscriber = TrackingSubscriber(
|
||||
@@ -124,18 +137,18 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0)])
|
||||
|
||||
cvs.value += 3
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(3)])
|
||||
|
||||
cvs.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(3),
|
||||
.completion(.failure(.oops))])
|
||||
@@ -179,51 +192,53 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.tracking.history, [.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.tracking.history,
|
||||
[.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.tracking.history, [.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty),
|
||||
.value(112),
|
||||
.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.tracking.history,
|
||||
[.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.subscription("CurrentValueSubject")])
|
||||
}
|
||||
|
||||
// Reactive Streams Spec: Rule #6
|
||||
@@ -241,24 +256,24 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
cvs.value = 42
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.send(completion: .failure("oops"))
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
@@ -279,23 +294,69 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
cvs.value = 44
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44)])
|
||||
|
||||
cvs.send(completion: .finished)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44),
|
||||
.completion(.finished)])
|
||||
|
||||
cvs.value = 1201
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(112),
|
||||
.value(44),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSubscriptionAfterCompletion() {
|
||||
let passthrough = Sut(0)
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
let subscriber = TrackingSubscriber()
|
||||
passthrough.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSendSubscription() {
|
||||
let subscription1 = CustomSubscription()
|
||||
let cvs = Sut(1)
|
||||
|
||||
cvs.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
|
||||
let subscriber1 = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
|
||||
cvs.subscribe(subscriber1)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
|
||||
let subscriber2 = TrackingSubscriber(receiveSubscription: { $0.request(.max(2)) })
|
||||
cvs.subscribe(subscriber2)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
XCTAssertEqual(subscriber2.history, [.subscription("CurrentValueSubject"),
|
||||
.value(1)])
|
||||
|
||||
cvs.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
|
||||
cvs.send(0)
|
||||
cvs.send(0)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
cvs.send(subscription: subscription2)
|
||||
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
@@ -310,17 +371,17 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.value += 1
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject")])
|
||||
|
||||
cvs.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
@@ -332,18 +393,18 @@ final class CurrentValueSubjectTests: XCTestCase {
|
||||
)
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
cvs.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0)])
|
||||
cvs.send(31)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("CurrentValueSubject"),
|
||||
.value(0),
|
||||
.value(31)])
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testSynchronization() {
|
||||
|
||||
@@ -13,4 +13,7 @@ import OpenCombine
|
||||
|
||||
extension JSONDecoder: TopLevelDecoder {}
|
||||
extension JSONEncoder: TopLevelEncoder {}
|
||||
|
||||
extension PropertyListDecoder: TopLevelDecoder {}
|
||||
extension PropertyListEncoder: TopLevelEncoder {}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// AssertCrashes.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 31.07.2019.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
extension XCTest {
|
||||
|
||||
var testcaseName: String {
|
||||
return String(describing: type(of: self))
|
||||
}
|
||||
|
||||
var testName: String {
|
||||
// Since on Apple platforms `self.name` has
|
||||
// format `-[XCTestCaseSubclassName testMethodName]`,
|
||||
// and on other platforms the format is
|
||||
// `XCTestCaseSubclassName.testMethodName`
|
||||
// we have this workaround in order to unify the names
|
||||
return name
|
||||
.components(separatedBy: testcaseName)
|
||||
.last!
|
||||
.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
}
|
||||
|
||||
// Taken from swift-corelibs-foundation and slightly modified for OpenCombine
|
||||
@available(macOS 10.13, iOS 8.0, *)
|
||||
func assertCrashes(within block: () throws -> Void) rethrows {
|
||||
#if !Xcode && !os(iOS) && !os(watchOS) && !os(tvOS)
|
||||
let childProcessEnvVariable = "OPENCOMBINE_TEST_PERFORM_ASSERT_CRASHES_BLOCKS"
|
||||
let childProcessEnvVariableOnValue = "YES"
|
||||
|
||||
let isChildProcess = ProcessInfo
|
||||
.processInfo
|
||||
.environment[childProcessEnvVariable] == childProcessEnvVariableOnValue
|
||||
|
||||
if isChildProcess {
|
||||
try block()
|
||||
} else {
|
||||
var arguments = ProcessInfo.processInfo.arguments
|
||||
let xctestUtilityPath = URL(fileURLWithPath: arguments[0])
|
||||
|
||||
let childProcess = Process()
|
||||
childProcess.executableURL = xctestUtilityPath
|
||||
|
||||
arguments.removeFirst()
|
||||
arguments.removeAll { $0.hasPrefix("OpenCombineTests.") || $0 == "-XCTest" }
|
||||
arguments.insert("OpenCombineTests.\(testcaseName)/\(testName)", at: 0)
|
||||
#if os(macOS)
|
||||
arguments.insert("-XCTest", at: 0)
|
||||
#endif
|
||||
childProcess.arguments = arguments
|
||||
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
environment[childProcessEnvVariable] = childProcessEnvVariableOnValue
|
||||
childProcess.environment = environment
|
||||
|
||||
func printDiagostics() {
|
||||
print("Parent process invocation:")
|
||||
print(ProcessInfo.processInfo.arguments.joined(separator: " "))
|
||||
print("Child process invocation:")
|
||||
print(
|
||||
([ProcessInfo.processInfo.arguments[0]] + arguments)
|
||||
.joined(separator: " ")
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
try childProcess.run()
|
||||
childProcess.waitUntilExit()
|
||||
if childProcess.terminationReason != .uncaughtSignal {
|
||||
XCTFail("Child process should have crashed: \(childProcess)")
|
||||
printDiagostics()
|
||||
}
|
||||
} catch {
|
||||
XCTFail("""
|
||||
Couldn't start child process for testing crash: \(childProcess) - \(error)
|
||||
""")
|
||||
printDiagostics()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -30,34 +30,33 @@ import OpenCombine
|
||||
/// publisher.subscribe(subscriber)
|
||||
///
|
||||
/// assert(subscription.history == [.requested(.max(42)), .cancelled])
|
||||
@available(macOS 10.15, *)
|
||||
typealias CustomPublisher = CustomPublisherBase<Int>
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias CustomPublisher = CustomPublisherBase<Int, TestingError>
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class CustomPublisherBase<Value: Equatable>: Publisher {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CustomPublisherBase<Output: Equatable, Failure: Error>: Publisher {
|
||||
|
||||
typealias Output = Value
|
||||
typealias Failure = TestingError
|
||||
|
||||
private var subscriber: AnySubscriber<Value, TestingError>?
|
||||
private(set) var subscriber: AnySubscriber<Output, Failure>?
|
||||
private(set) var erasedSubscriber: Any?
|
||||
private let subscription: Subscription?
|
||||
|
||||
init(subscription: Subscription?) {
|
||||
self.subscription = subscription
|
||||
}
|
||||
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Failure == Downstream.Failure, Output == Downstream.Input
|
||||
{
|
||||
self.subscriber = AnySubscriber(subscriber)
|
||||
erasedSubscriber = subscriber
|
||||
subscription.map(subscriber.receive(subscription:))
|
||||
}
|
||||
|
||||
func send(_ value: Value) -> Subscribers.Demand {
|
||||
return subscriber!.receive(value)
|
||||
func send(_ value: Output) -> Subscribers.Demand {
|
||||
return subscriber?.receive(value) ?? .none
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<TestingError>) {
|
||||
func send(completion: Subscribers.Completion<Failure>) {
|
||||
subscriber!.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import OpenCombine
|
||||
///
|
||||
/// In order to inject `CustomSubscription` into the chain of subscriptions,
|
||||
/// use the `CustomSubscriber` class.
|
||||
@available(macOS 10.15, *)
|
||||
final class CustomSubscription: Subscription {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CustomSubscription: Subscription, CustomStringConvertible {
|
||||
|
||||
enum Event: Equatable, CustomStringConvertible {
|
||||
case requested(Subscribers.Demand)
|
||||
@@ -26,9 +26,9 @@ final class CustomSubscription: Subscription {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .requested(let demand):
|
||||
return "requested(\(demand))"
|
||||
return ".requested(.\(demand))"
|
||||
case .cancelled:
|
||||
return "cancelled"
|
||||
return ".cancelled"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@ final class CustomSubscription: Subscription {
|
||||
private(set) var history: [Event] = []
|
||||
|
||||
private let _requested: ((Subscribers.Demand) -> Void)?
|
||||
private let _canceled: (() -> Void)?
|
||||
private let _cancelled: (() -> Void)?
|
||||
|
||||
init(onRequest: ((Subscribers.Demand) -> Void)? = nil,
|
||||
onCancel: (() -> Void)? = nil) {
|
||||
_requested = onRequest
|
||||
_canceled = onCancel
|
||||
_cancelled = onCancel
|
||||
}
|
||||
|
||||
var lastRequested: Subscribers.Demand? {
|
||||
@@ -56,7 +56,7 @@ final class CustomSubscription: Subscription {
|
||||
}.last
|
||||
}
|
||||
|
||||
var canceled = false
|
||||
var cancelled = false
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
history.append(.requested(demand))
|
||||
@@ -65,7 +65,9 @@ final class CustomSubscription: Subscription {
|
||||
|
||||
func cancel() {
|
||||
history.append(.cancelled)
|
||||
canceled = true
|
||||
_canceled?()
|
||||
cancelled = true
|
||||
_cancelled?()
|
||||
}
|
||||
|
||||
var description: String { return "CustomSubscription" }
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
class TestEncoder: TopLevelEncoder {
|
||||
final class TestEncoder: TopLevelEncoder {
|
||||
typealias Output = Int
|
||||
|
||||
private var nextEncoded = 1
|
||||
|
||||
var encoded: [Int: Any] = [:]
|
||||
var encoded: [Int : Any] = [:]
|
||||
|
||||
var handleEncode: ((Any) -> Int?)?
|
||||
var handleEncode: ((Any) throws -> Int?)?
|
||||
|
||||
func encode<EncodableType: Encodable>(_ value: EncodableType) throws -> Int {
|
||||
var keyNumber = nextEncoded
|
||||
if let number = handleEncode?(value) {
|
||||
if let number = try handleEncode?(value) {
|
||||
keyNumber = number
|
||||
} else {
|
||||
nextEncoded += 1
|
||||
@@ -32,19 +32,18 @@ class TestEncoder: TopLevelEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
class TestDecoder: TopLevelDecoder {
|
||||
final class TestDecoder: TopLevelDecoder {
|
||||
typealias Input = Int
|
||||
|
||||
static let error = "Could not decode" as TestingError
|
||||
|
||||
var handleDecode: ((Int) -> Any?)?
|
||||
var handleDecode: ((Int) throws -> Any?)?
|
||||
|
||||
func decode<DecodablyType: Decodable>(
|
||||
_ type: DecodablyType.Type,
|
||||
from data: Int)
|
||||
throws -> DecodablyType
|
||||
{
|
||||
if let value = handleDecode?(data), let mappedValue = value as? DecodablyType {
|
||||
func decode<Decodable: Swift.Decodable>(
|
||||
_ type: Decodable.Type,
|
||||
from data: Int
|
||||
) throws -> Decodable {
|
||||
if let value = try handleDecode?(data), let mappedValue = value as? Decodable {
|
||||
return mappedValue
|
||||
}
|
||||
throw TestDecoder.error
|
||||
|
||||
@@ -11,7 +11,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias DisposeBag = TrackingCollection<AnyCancellable>
|
||||
|
||||
final class TrackingCollection<Element> {
|
||||
@@ -224,3 +224,34 @@ extension TrackingCollection: RangeReplaceableCollection {
|
||||
try storage.removeAll(where: shouldBeRemoved)
|
||||
}
|
||||
}
|
||||
|
||||
final class TrackingRangeExpression<RangeExpression: Swift.RangeExpression>
|
||||
: Swift.RangeExpression
|
||||
where RangeExpression.Bound == Int
|
||||
{
|
||||
enum Event: Equatable {
|
||||
case contains(Int)
|
||||
case relativeTo(Range<Int>?)
|
||||
}
|
||||
|
||||
typealias Bound = Int
|
||||
|
||||
private let underlying: RangeExpression
|
||||
private(set) var history: [Event] = []
|
||||
|
||||
init(_ underlying: RangeExpression) {
|
||||
self.underlying = underlying
|
||||
}
|
||||
|
||||
func relative<Elements: Collection>(
|
||||
to collection: Elements
|
||||
) -> Range<Int> where Elements.Index == Int {
|
||||
history.append(.relativeTo(collection as? Range<Int>))
|
||||
return underlying.relative(to: collection)
|
||||
}
|
||||
|
||||
func contains(_ element: Int) -> Bool {
|
||||
history.append(.contains(element))
|
||||
return underlying.contains(element)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import OpenCombine
|
||||
/// `TrackingSubscriber.Event.subscription(Subscription.empty)`
|
||||
/// is considered equal to any other subscription no matter what the subscription object
|
||||
/// actually is.
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias TrackingSubscriber = TrackingSubscriberBase<Int, TestingError>
|
||||
|
||||
/// `TrackingSubscriber` records every event like "receiveSubscription",
|
||||
@@ -36,22 +36,21 @@ typealias TrackingSubscriber = TrackingSubscriberBase<Int, TestingError>
|
||||
/// `TrackingSubscriber.Event.subscription(Subscription.empty)`
|
||||
/// is considered equal to any other subscription no matter what the subscription object
|
||||
/// actually is.
|
||||
@available(macOS 10.15, *)
|
||||
final class TrackingSubscriberBase<Value: Equatable,
|
||||
Failure: Error>
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class TrackingSubscriberBase<Value: Equatable, Failure: Error>
|
||||
: Subscriber,
|
||||
CustomStringConvertible
|
||||
{
|
||||
|
||||
enum Event: Equatable, CustomStringConvertible {
|
||||
case subscription(Subscription)
|
||||
case subscription(StringSubscription)
|
||||
case value(Value)
|
||||
case completion(Subscribers.Completion<Failure>)
|
||||
|
||||
static func == (lhs: Event, rhs: Event) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.subscription, .subscription):
|
||||
return true
|
||||
case let (.subscription(lhs), .subscription(rhs)):
|
||||
return lhs == rhs
|
||||
case let (.value(lhs), .value(rhs)):
|
||||
return lhs == rhs
|
||||
case let (.completion(lhs), .completion(rhs)):
|
||||
@@ -70,14 +69,14 @@ final class TrackingSubscriberBase<Value: Equatable,
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .subscription:
|
||||
return "subscription"
|
||||
case .subscription(let subscription):
|
||||
return ".subscription(\"\(subscription)\")"
|
||||
case .value(let value):
|
||||
return "value(\(value))"
|
||||
return ".value(\(value))"
|
||||
case .completion(.finished):
|
||||
return "finished"
|
||||
return ".completion(.finished)"
|
||||
case .completion(.failure(let error)):
|
||||
return "failure(\(error))"
|
||||
return ".completion(.failure(\(error)))"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +91,8 @@ final class TrackingSubscriberBase<Value: Equatable,
|
||||
|
||||
/// A lazy view on `history` with all events except subscriptions filtered out
|
||||
var subscriptions: LazyMapSequence<
|
||||
LazyFilterSequence<LazyMapSequence<[Event], Subscription?>>, Subscription
|
||||
LazyFilterSequence<LazyMapSequence<[Event], StringSubscription?>>,
|
||||
StringSubscription
|
||||
> {
|
||||
return history.lazy.compactMap {
|
||||
if case .subscription(let s) = $0 {
|
||||
@@ -143,7 +143,7 @@ final class TrackingSubscriberBase<Value: Equatable,
|
||||
}
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
history.append(.subscription(subscription))
|
||||
history.append(.subscription(.init(subscription)))
|
||||
_receiveSubscription?(subscription)
|
||||
}
|
||||
|
||||
@@ -166,21 +166,25 @@ final class TrackingSubscriberBase<Value: Equatable,
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class TrackingSubject<Value: Equatable>: Subject {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
typealias TrackingSubject<Output: Equatable> = TrackingSubjectBase<Output, TestingError>
|
||||
|
||||
typealias Failure = TestingError
|
||||
|
||||
typealias Output = Value
|
||||
|
||||
enum Event: Equatable {
|
||||
case subscriber(CombineIdentifier)
|
||||
case value(Value)
|
||||
case completion(Subscribers.Completion<TestingError>)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class TrackingSubjectBase<Output: Equatable, Failure: Error>
|
||||
: Subject,
|
||||
CustomStringConvertible
|
||||
{
|
||||
enum Event: Equatable, CustomStringConvertible {
|
||||
case subscriber
|
||||
case subscription(StringSubscription)
|
||||
case value(Output)
|
||||
case completion(Subscribers.Completion<Failure>)
|
||||
|
||||
static func == (lhs: Event, rhs: Event) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.subscriber(lhs), .subscriber(rhs)):
|
||||
case (.subscriber, .subscriber):
|
||||
return true
|
||||
case let (.subscription(lhs), .subscription(rhs)):
|
||||
return lhs == rhs
|
||||
case let (.value(lhs), .value(rhs)):
|
||||
return lhs == rhs
|
||||
@@ -189,7 +193,7 @@ final class TrackingSubject<Value: Equatable>: Subject {
|
||||
case (.finished, .finished):
|
||||
return true
|
||||
case let (.failure(lhs), .failure(rhs)):
|
||||
return lhs == rhs
|
||||
return (lhs as? TestingError) == (rhs as? TestingError)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -197,21 +201,119 @@ final class TrackingSubject<Value: Equatable>: Subject {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .subscriber:
|
||||
return ".subscriber"
|
||||
case .subscription(let description):
|
||||
return ".subscription(\"\(description)\")"
|
||||
case .value(let value):
|
||||
return ".value(\(value))"
|
||||
case .completion(.finished):
|
||||
return ".completion(.finished)"
|
||||
case .completion(.failure(let error)):
|
||||
return ".completion(.failure(\(error))"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let _passthrough = PassthroughSubject<Output, Failure>()
|
||||
private(set) var history: [Event] = []
|
||||
private let _receiveSubscriber: ((CustomCombineIdentifierConvertible) -> Void)?
|
||||
private let _onDeinit: (() -> Void)?
|
||||
|
||||
func send(_ value: Value) {
|
||||
history.append(.value(value))
|
||||
init(receiveSubscriber: ((CustomCombineIdentifierConvertible) -> Void)? = nil,
|
||||
onDeinit: (() -> Void)? = nil) {
|
||||
_receiveSubscriber = receiveSubscriber
|
||||
_onDeinit = onDeinit
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<TestingError>) {
|
||||
deinit {
|
||||
_onDeinit?()
|
||||
}
|
||||
|
||||
func send(subscription: Subscription) {
|
||||
history.append(.subscription(.subscription(subscription)))
|
||||
_passthrough.send(subscription: subscription)
|
||||
}
|
||||
|
||||
func send(_ value: Output) {
|
||||
history.append(.value(value))
|
||||
_passthrough.send(value)
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<Failure>) {
|
||||
history.append(.completion(completion))
|
||||
_passthrough.send(completion: completion)
|
||||
}
|
||||
|
||||
func receive<SubscriberType: Subscriber>(subscriber: SubscriberType)
|
||||
where Failure == SubscriberType.Failure, Output == SubscriberType.Input
|
||||
{
|
||||
history.append(.subscriber(subscriber.combineIdentifier))
|
||||
_receiveSubscriber?(subscriber)
|
||||
history.append(.subscriber)
|
||||
_passthrough.subscribe(subscriber)
|
||||
}
|
||||
|
||||
var description: String { return "TrackingSubject" }
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
enum StringSubscription: Subscription,
|
||||
CustomStringConvertible,
|
||||
ExpressibleByStringLiteral {
|
||||
|
||||
case string(String)
|
||||
case subscription(Subscription)
|
||||
|
||||
init(_ subscription: Subscription) {
|
||||
self = .subscription(subscription)
|
||||
}
|
||||
|
||||
init(stringLiteral value: String) {
|
||||
self = .string(value)
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .string(let string):
|
||||
return string
|
||||
case .subscription(let subscription):
|
||||
return String(describing: subscription)
|
||||
}
|
||||
}
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
underlying?.request(demand)
|
||||
}
|
||||
|
||||
var combineIdentifier: CombineIdentifier {
|
||||
switch self {
|
||||
case .subscription(let subscription):
|
||||
return subscription.combineIdentifier
|
||||
case .string:
|
||||
fatalError("String has no combineIdentifier")
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
underlying?.cancel()
|
||||
}
|
||||
|
||||
var underlying: Subscription? {
|
||||
switch self {
|
||||
case .string:
|
||||
return nil
|
||||
case .subscription(let underlying):
|
||||
return underlying
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
extension StringSubscription: Equatable {
|
||||
static func == (lhs: StringSubscription, rhs: StringSubscription) -> Bool {
|
||||
return lhs.description == rhs.description
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class ImmediateSchedulerTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
|
||||
@@ -13,15 +13,18 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PassthroughSubjectTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testRequestingDemand", testRequestingDemand),
|
||||
("testCrashOnZeroInitialDemand", testCrashOnZeroInitialDemand),
|
||||
("testSendFailureCompletion", testSendFailureCompletion),
|
||||
("testMultipleSubscriptions", testMultipleSubscriptions),
|
||||
("testMultipleCompletions", testMultipleCompletions),
|
||||
("testValuesAfterCompletion", testValuesAfterCompletion),
|
||||
("testSubscriptionAfterCompletion", testSubscriptionAfterCompletion),
|
||||
("testSendSubscription", testSendSubscription),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testSynchronization", testSynchronization),
|
||||
]
|
||||
@@ -31,8 +34,8 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
// Reactive Streams Spec: Rules #1, #2, #9
|
||||
func testRequestingDemand() {
|
||||
|
||||
let initialDemands: [Subscribers.Demand] = [
|
||||
.max(0),
|
||||
let initialDemands: [Subscribers.Demand?] = [
|
||||
nil,
|
||||
.max(1),
|
||||
.max(2),
|
||||
.max(10),
|
||||
@@ -66,7 +69,7 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
let subscriber = AnySubscriber<Int, TestingError>(
|
||||
receiveSubscription: { subscription in
|
||||
subscriptions.append(subscription)
|
||||
subscription.request(initialDemand)
|
||||
initialDemand.map(subscription.request)
|
||||
},
|
||||
receiveValue: { value in
|
||||
defer { i += 1 }
|
||||
@@ -104,6 +107,16 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(numberOfInputsHistory, expectedNumberOfInputsHistory)
|
||||
}
|
||||
|
||||
func testCrashOnZeroInitialDemand() {
|
||||
assertCrashes {
|
||||
let subscriber = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.none) }
|
||||
)
|
||||
|
||||
Sut().subscribe(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
func testSendFailureCompletion() {
|
||||
let cvs = Sut()
|
||||
let subscriber = TrackingSubscriber(
|
||||
@@ -114,11 +127,11 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
|
||||
cvs.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject")])
|
||||
|
||||
cvs.send(completion: .failure(.oops))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
@@ -267,6 +280,49 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(completions.count, 1)
|
||||
}
|
||||
|
||||
func testSubscriptionAfterCompletion() {
|
||||
let passthrough = Sut()
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
let subscriber = TrackingSubscriber()
|
||||
passthrough.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSendSubscription() {
|
||||
let subscription1 = CustomSubscription()
|
||||
let passthrough = Sut()
|
||||
|
||||
passthrough.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [])
|
||||
|
||||
let subscriber1 = TrackingSubscriber(receiveSubscription: { $0.request(.max(1)) })
|
||||
passthrough.subscribe(subscriber1)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("PassthroughSubject")])
|
||||
|
||||
let subscriber2 = TrackingSubscriber(receiveSubscription: { $0.request(.max(2)) })
|
||||
passthrough.subscribe(subscriber2)
|
||||
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(subscriber1.history, [.subscription("PassthroughSubject")])
|
||||
XCTAssertEqual(subscriber2.history, [.subscription("PassthroughSubject")])
|
||||
|
||||
passthrough.send(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.history, [.requested(.unlimited),
|
||||
.requested(.unlimited)])
|
||||
|
||||
passthrough.send(0)
|
||||
passthrough.send(0)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
passthrough.send(subscription: subscription2)
|
||||
XCTAssertEqual(subscription2.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
@@ -285,7 +341,7 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 1)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
do {
|
||||
let passthrough = Sut()
|
||||
@@ -297,7 +353,7 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
@@ -316,9 +372,9 @@ final class PassthroughSubjectTests: XCTestCase {
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testSynchronization() {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// PublisherTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 08.07.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PublisherTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSubscribeSubscriber", testSubscribeSubscriber),
|
||||
("testSubscribeSubject", testSubscribeSubject),
|
||||
("testSubjectSubscriber", testSubjectSubscriber),
|
||||
]
|
||||
|
||||
func testSubscribeSubscriber() {
|
||||
|
||||
final class TrivialPublisher: Publisher {
|
||||
typealias Output = Int
|
||||
typealias Failure = TestingError
|
||||
|
||||
private(set) var counter = 0
|
||||
|
||||
func receive<SomeSubscriber: Subscriber>(
|
||||
subscriber: SomeSubscriber
|
||||
) where Failure == SomeSubscriber.Failure, Output == SomeSubscriber.Input {
|
||||
counter += 1
|
||||
}
|
||||
}
|
||||
|
||||
let publisher = TrivialPublisher()
|
||||
let subscriber = TrackingSubscriber()
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(publisher.counter, 1)
|
||||
XCTAssert(subscriber.history.isEmpty)
|
||||
}
|
||||
|
||||
func testSubscribeSubject() {
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
|
||||
let subject = TrackingSubject<Int>()
|
||||
|
||||
let cancellable = publisher.subscribe(subject)
|
||||
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
XCTAssertEqual(subject.history, [.subscription("Subject")])
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .none)
|
||||
XCTAssertEqual(publisher.send(1), .none)
|
||||
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
XCTAssertEqual(subject.history, [.subscription("Subject"),
|
||||
.value(0),
|
||||
.value(1)])
|
||||
|
||||
cancellable.cancel()
|
||||
|
||||
XCTAssertEqual(publisher.send(2), .none)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.cancelled])
|
||||
XCTAssertEqual(subject.history, [.subscription("Subject"), .value(0), .value(1)])
|
||||
}
|
||||
|
||||
func testSubjectSubscriber() throws {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
|
||||
var subjectDestroyed = false
|
||||
do {
|
||||
let subject = TrackingSubject<Int>(onDeinit: { subjectDestroyed = true })
|
||||
|
||||
let cancellable = publisher.subscribe(subject)
|
||||
|
||||
try withExtendedLifetime(cancellable) {
|
||||
|
||||
let subjectSubscription =
|
||||
try XCTUnwrap(publisher.erasedSubscriber as? Subscription)
|
||||
|
||||
XCTAssertEqual(String(describing: subjectSubscription), "Subject")
|
||||
|
||||
subjectSubscription.request(.max(42))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42))])
|
||||
|
||||
subjectSubscription.cancel()
|
||||
subjectSubscription.cancel()
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
|
||||
|
||||
subjectSubscription.request(.max(37))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
|
||||
}
|
||||
}
|
||||
|
||||
XCTAssert(subjectDestroyed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// CountTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 6/25/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CountTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSendsCorrectCount", testSendsCorrectCount),
|
||||
("testCountWaitsUntilFinishedToSend", testCountWaitsUntilFinishedToSend),
|
||||
("testAddingSubscriberRequestsUnlimitedDemand",
|
||||
testAddingSubscriberRequestsUnlimitedDemand),
|
||||
("testReceivesSubscriptionBeforeRequestingUpstream",
|
||||
testReceivesSubscriptionBeforeRequestingUpstream)
|
||||
]
|
||||
|
||||
func testSendsCorrectCount() {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let countPublisher = publisher.count()
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(42)) }
|
||||
)
|
||||
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
|
||||
countPublisher.subscribe(tracking)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count")])
|
||||
|
||||
let sendAmount = Int.random(in: 1...1000)
|
||||
for _ in 0..<sendAmount {
|
||||
_ = publisher.send(3)
|
||||
}
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count")])
|
||||
|
||||
publisher.send(completion: .finished)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count"),
|
||||
.value(sendAmount),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testCountWaitsUntilFinishedToSend() {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let countPublisher = publisher.count()
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(42)) }
|
||||
)
|
||||
|
||||
countPublisher.subscribe(tracking)
|
||||
|
||||
_ = publisher.send(1)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count")])
|
||||
|
||||
_ = publisher.send(2)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count")])
|
||||
|
||||
_ = publisher.send(0)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count")])
|
||||
|
||||
publisher.send(completion: .finished)
|
||||
XCTAssertEqual(tracking.history, [.subscription("Count"),
|
||||
.value(3),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testDemand() {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let countPublisher = publisher.count()
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(42))
|
||||
downstreamSubscription = $0
|
||||
},
|
||||
receiveValue: { _ in .max(4) }
|
||||
)
|
||||
|
||||
countPublisher.subscribe(tracking)
|
||||
|
||||
XCTAssertNotNil(downstreamSubscription)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .max(0))
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
|
||||
XCTAssertEqual(publisher.send(2), .max(0))
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
|
||||
downstreamSubscription?.request(.max(95))
|
||||
downstreamSubscription?.request(.max(5))
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
|
||||
downstreamSubscription?.cancel()
|
||||
downstreamSubscription?.cancel()
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
|
||||
downstreamSubscription?.request(.max(50))
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testAddingSubscriberRequestsUnlimitedDemand() {
|
||||
// When
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let countPublisher = publisher.count()
|
||||
let tracking = TrackingSubscriber()
|
||||
|
||||
// Given
|
||||
XCTAssertEqual(subscription.history, [])
|
||||
countPublisher.subscribe(tracking)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
}
|
||||
|
||||
func testReceivesSubscriptionBeforeRequestingUpstream() {
|
||||
let upstreamRequest = "Requested upstream subscription"
|
||||
let receiveDownstream = "Receive downstream"
|
||||
var receiveOrder: [String] = []
|
||||
|
||||
let subscription = CustomSubscription(onRequest: { _ in
|
||||
receiveOrder.append(upstreamRequest)
|
||||
})
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let countPublisher = publisher.count()
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { _ in
|
||||
receiveOrder.append(receiveDownstream)
|
||||
})
|
||||
|
||||
countPublisher.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(receiveOrder, [receiveDownstream, upstreamRequest])
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DecodeTests: XCTestCase {
|
||||
static let allTests = [
|
||||
("testDecodeWorks", testDecodeWorks),
|
||||
("testDownstraemReceivesFailure", testDownstreamReceivesFailure),
|
||||
("testDecodingSuccess", testDecodingSuccess),
|
||||
("testDecodingFailure", testDecodingFailure),
|
||||
("testDemand", testDemand)
|
||||
]
|
||||
|
||||
@@ -30,11 +30,10 @@ final class DecodeTests: XCTestCase {
|
||||
jsonDecoder = TestDecoder()
|
||||
}
|
||||
|
||||
func testDecodeWorks() throws {
|
||||
// Given
|
||||
let data = 78 // Represents decodable data
|
||||
func testDecodingSuccess() throws {
|
||||
let data = 78
|
||||
let subject = PassthroughSubject<Int, Error>()
|
||||
let publisher = subject.decode(type: [String: String].self, decoder: jsonDecoder)
|
||||
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
|
||||
let subscriber = TrackingSubscriberBase<[String: String], Error>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
@@ -45,53 +44,41 @@ final class DecodeTests: XCTestCase {
|
||||
return nil
|
||||
}
|
||||
|
||||
// When
|
||||
publisher.subscribe(subscriber)
|
||||
subject.send(data)
|
||||
subject.send(completion: .finished)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
.value(testValue)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Decode"),
|
||||
.value(testValue),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testDownstreamReceivesFailure() {
|
||||
// Given
|
||||
func testDecodingFailure() {
|
||||
let failData = 95
|
||||
let subject = PassthroughSubject<Int, Error>()
|
||||
let publisher = subject.decode(type: [String: String].self, decoder: jsonDecoder)
|
||||
let publisher = subject.decode(type: [String : String].self, decoder: jsonDecoder)
|
||||
let subscriber = TrackingSubscriberBase<[String: String], Error>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
|
||||
// When
|
||||
publisher.subscribe(subscriber)
|
||||
subject.send(failData)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Decode"),
|
||||
.completion(.failure(TestDecoder.error))])
|
||||
}
|
||||
|
||||
func testDemand() {
|
||||
// `CustomSubscription` tracks all the requests and cancellations
|
||||
// in its `history` property
|
||||
let subscription = CustomSubscription()
|
||||
|
||||
// `CustomPublisher` sends the subscription object it has been initialized with
|
||||
// to whoever subscribed to the `CustomPublisher`.
|
||||
let publisher = CustomPublisherBase<Int>(subscription: subscription)
|
||||
let publisher = CustomPublisherBase<Int, TestingError>(subscription: subscription)
|
||||
|
||||
// `_Decode` helper will receive the `CustomSubscription `
|
||||
let decode = publisher.decode(type: [String : String].self,
|
||||
decoder: jsonDecoder)
|
||||
|
||||
// This is actually `_Decode`
|
||||
var downstreamSubscription: Subscription?
|
||||
|
||||
// `TrackingSubscriber` records every event like "receiveSubscription",
|
||||
// "receiveValue" and "receiveCompletion" into its `history` property,
|
||||
// optionally executing the provided callbacks.
|
||||
let tracking = TrackingSubscriberBase<[String: String], Error>(
|
||||
let tracking = TrackingSubscriberBase<[String : String], Error>(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(42))
|
||||
downstreamSubscription = $0
|
||||
@@ -100,8 +87,10 @@ final class DecodeTests: XCTestCase {
|
||||
)
|
||||
|
||||
decode.subscribe(tracking)
|
||||
XCTAssertNotNil(downstreamSubscription) // Removes unused variable warning
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42))])
|
||||
XCTAssertNotNil(downstreamSubscription)
|
||||
|
||||
XCTAssertEqual(publisher.send(10), .none)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)), .cancelled])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// DeferredTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/7/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
//import Combine
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DeferredTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testDeferredCreatedAfterSubscription",
|
||||
testDeferredCreatedAfterSubscription)
|
||||
]
|
||||
|
||||
func testDeferredCreatedAfterSubscription() {
|
||||
var deferredPublisherCreatedCount = 0
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let deferred = Deferred { () -> CustomPublisher in
|
||||
deferredPublisherCreatedCount += 1
|
||||
return publisher
|
||||
}
|
||||
|
||||
let tracking = TrackingSubscriber()
|
||||
|
||||
XCTAssertEqual(deferredPublisherCreatedCount, 0)
|
||||
XCTAssertEqual(tracking.history, [])
|
||||
|
||||
deferred.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CustomSubscription")])
|
||||
XCTAssertEqual(deferredPublisherCreatedCount, 1)
|
||||
|
||||
deferred.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(deferredPublisherCreatedCount, 2)
|
||||
|
||||
subscription.request(.unlimited)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("CustomSubscription"),
|
||||
.subscription("CustomSubscription")])
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,14 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
// TODO: add tests from https://github.com/ReactiveX/RxJava/blob/83f2bd771ee172a2154e0fb30c5ffcaf8f71433c/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class DropWhileTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testDropWhile", testDropWhile),
|
||||
("testTryDropWhileFailureBecauseOfThrow", testTryDropWhileFailureBecauseOfThrow),
|
||||
("testTryDropWhileFailureOnCompletion", testTryDropWhileFailureOnCompletion),
|
||||
("testTryDropWhileSuccess", testTryDropWhileSuccess),
|
||||
("testDemand", testDemand),
|
||||
("testTryDropWhileCancelsUpstreamOnThrow",
|
||||
testTryDropWhileCancelsUpstreamOnThrow),
|
||||
@@ -48,7 +47,7 @@ final class DropWhileTests: XCTestCase {
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(10)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
|
||||
.value(7),
|
||||
.value(8),
|
||||
.value(9),
|
||||
@@ -82,7 +81,7 @@ final class DropWhileTests: XCTestCase {
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription(Subscriptions.empty),
|
||||
[.subscription("TryDropWhile"),
|
||||
.completion(.failure("too much" as TestingError))])
|
||||
|
||||
XCTAssertEqual(counter, 3)
|
||||
@@ -101,10 +100,36 @@ final class DropWhileTests: XCTestCase {
|
||||
publisher.send(2)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription(Subscriptions.empty),
|
||||
[.subscription("TryDropWhile"),
|
||||
.completion(.failure(TestingError.oops))])
|
||||
}
|
||||
|
||||
func testTryDropWhileSuccess() {
|
||||
|
||||
let publisher = PassthroughSubject<Int, Error>()
|
||||
let drop = publisher.tryDrop { $0.isMultiple(of: 2) }
|
||||
|
||||
let tracking = TrackingSubscriberBase<Int, Error>(
|
||||
receiveSubscription: { $0.request(.max(2)) }
|
||||
)
|
||||
|
||||
publisher.send(1)
|
||||
drop.subscribe(tracking)
|
||||
publisher.send(0)
|
||||
publisher.send(2)
|
||||
publisher.send(3)
|
||||
publisher.send(4)
|
||||
publisher.send(5)
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(8)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("TryDropWhile"),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testDemand() {
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
@@ -122,40 +147,54 @@ final class DropWhileTests: XCTestCase {
|
||||
drop.subscribe(tracking)
|
||||
|
||||
XCTAssertNotNil(downstreamSubscription)
|
||||
dump(type(of: downstreamSubscription!))
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42))])
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .max(1))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42))])
|
||||
|
||||
XCTAssertEqual(publisher.send(2), .max(1))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42))])
|
||||
|
||||
downstreamSubscription?.request(.max(95))
|
||||
downstreamSubscription?.request(.max(5))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5))])
|
||||
|
||||
XCTAssertEqual(publisher.send(3), .max(145)) // 145 = 42 + 95 + 5 + 3
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(publisher.send(3), .max(4))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5))])
|
||||
|
||||
downstreamSubscription?.request(.max(121))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)), .requested(.max(121))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121))])
|
||||
|
||||
XCTAssertEqual(publisher.send(7), .max(4))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)), .requested(.max(121))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121))])
|
||||
|
||||
downstreamSubscription?.cancel()
|
||||
downstreamSubscription?.cancel()
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)),
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121)),
|
||||
.cancelled])
|
||||
|
||||
downstreamSubscription?.request(.max(50))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)),
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(42)),
|
||||
.requested(.max(95)),
|
||||
.requested(.max(5)),
|
||||
.requested(.max(121)),
|
||||
.cancelled])
|
||||
|
||||
XCTAssertEqual(publisher.send(8), .max(4))
|
||||
XCTAssertEqual(publisher.send(8), .none)
|
||||
}
|
||||
|
||||
func testTryDropWhileCancelsUpstreamOnThrow() {
|
||||
@@ -169,15 +208,18 @@ final class DropWhileTests: XCTestCase {
|
||||
)
|
||||
|
||||
drop.subscribe(tracking)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(publisher.send(100), .none)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)), .cancelled])
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
publisher.send(completion: .finished)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1)), .cancelled])
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription(Subscriptions.empty),
|
||||
.completion(.failure("too much" as TestingError)),
|
||||
.completion(.finished)])
|
||||
[.subscription("TryDropWhile"),
|
||||
.completion(.failure("too much" as TestingError))])
|
||||
XCTAssertEqual(publisher.send(12), .none)
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("TryDropWhile"),
|
||||
.completion(.failure("too much" as TestingError))])
|
||||
}
|
||||
|
||||
func testDropWhileCompletion() {
|
||||
@@ -190,12 +232,103 @@ final class DropWhileTests: XCTestCase {
|
||||
)
|
||||
|
||||
drop.subscribe(tracking)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(completion: .finished)
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(1))])
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
|
||||
.completion(.finished),
|
||||
.completion(.finished)])
|
||||
|
||||
publisher.send(completion: .failure(.oops))
|
||||
publisher.send(completion: .failure(.oops))
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
|
||||
.completion(.finished),
|
||||
.completion(.finished),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let dropWhile = publisher.drop(while: { _ in true })
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
})
|
||||
|
||||
dropWhile.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
downstreamSubscription?.request(.unlimited)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
|
||||
publisher.send(completion: .failure(.oops))
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
XCTAssertEqual(tracking.history, [.subscription("DropWhile"),
|
||||
.completion(.failure(.oops)),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
|
||||
let onDeinit = { deinitCounter += 1 }
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let dropWhile = passthrough.drop(while: { _ in true })
|
||||
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
dropWhile.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
passthrough.send(31)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 0)
|
||||
passthrough.send(completion: .failure("failure"))
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 1)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let dropWhile = passthrough.drop(while: { _ in true })
|
||||
let emptySubscriber = TrackingSubscriber(onDeinit: onDeinit)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
dropWhile.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 0)
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let dropWhile = passthrough.drop(while: { _ in true })
|
||||
let emptySubscriber = TrackingSubscriber(
|
||||
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
dropWhile.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
passthrough.send(31)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 0)
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,44 +13,66 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class EmptyTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testEmpty", testEmpty),
|
||||
("testImmediatelyCancel", testImmediatelyCancel),
|
||||
("testEquatable", testEquatable),
|
||||
]
|
||||
|
||||
func testEmpty() {
|
||||
|
||||
let completesImmediately = Publishers.Empty(completeImmediately: true,
|
||||
outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
let completesImmediately = Empty(completeImmediately: true,
|
||||
outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
let subscriber = TrackingSubscriber()
|
||||
completesImmediately.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
|
||||
let doesNotComplete = Publishers.Empty(completeImmediately: false,
|
||||
outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
let doesNotComplete = Empty(completeImmediately: false,
|
||||
outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
|
||||
doesNotComplete.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished),
|
||||
.subscription(Subscriptions.empty)])
|
||||
.subscription("Empty")])
|
||||
}
|
||||
|
||||
func testImmediatelyCancel() {
|
||||
let completesImmediately = Publishers.Empty(outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
let completesImmediately = Empty(outputType: Int.self,
|
||||
failureType: TestingError.self)
|
||||
|
||||
let subscriber = TrackingSubscriber(receiveSubscription: { $0.cancel() })
|
||||
completesImmediately.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testEquatable() {
|
||||
XCTAssertEqual(Empty(completeImmediately: true,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self),
|
||||
Empty(completeImmediately: true,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self))
|
||||
XCTAssertEqual(Empty(completeImmediately: false,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self),
|
||||
Empty(completeImmediately: false,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self))
|
||||
XCTAssertNotEqual(Empty(completeImmediately: true,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self),
|
||||
Empty(completeImmediately: false,
|
||||
outputType: Int.self,
|
||||
failureType: Error.self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class EncodeTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testEncodeWorks", testEncodeWorks),
|
||||
("testEncodingSuccess", testEncodingSuccess),
|
||||
("testEncodingFailure", testEncodingFailure),
|
||||
("testDemand", testDemand),
|
||||
("testEncodeSuccessHistory", testEncodeSuccessHistory)
|
||||
("testEncodeSuccessHistory", testEncodeSuccessHistory),
|
||||
]
|
||||
|
||||
private var encoder = TestEncoder()
|
||||
@@ -30,63 +32,68 @@ final class EncodeTests: XCTestCase {
|
||||
decoder = TestDecoder()
|
||||
}
|
||||
|
||||
func testEncodeWorks() throws {
|
||||
// Given
|
||||
let testValue = ["test": "TestDecodable"]
|
||||
let subject = PassthroughSubject<[String: String], Error>()
|
||||
func testEncodingSuccess() throws {
|
||||
let testValue = ["test" : "TestDecodable"]
|
||||
let subject = PassthroughSubject<[String : String], Error>()
|
||||
let publisher = subject.encode(encoder: encoder)
|
||||
let subscriber = TrackingSubscriberBase<Int, Error>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
|
||||
// When
|
||||
publisher.subscribe(subscriber)
|
||||
subject.send(testValue)
|
||||
|
||||
// Then
|
||||
XCTAssert(encoder.encoded.first?.value as? [String: String] == testValue)
|
||||
XCTAssertEqual(encoder.encoded.first?.value as? [String : String], testValue)
|
||||
}
|
||||
|
||||
func testEncodingFailure() throws {
|
||||
let testValue = ["test" : "TestDecodable"]
|
||||
let subject = PassthroughSubject<[String : String], Error>()
|
||||
|
||||
encoder.handleEncode = { _ in throw TestingError.oops }
|
||||
|
||||
let publisher = subject.encode(encoder: encoder)
|
||||
let subscriber = TrackingSubscriberBase<Int, Error>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
subject.send(testValue)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Encode"),
|
||||
.completion(.failure(TestingError.oops))])
|
||||
}
|
||||
|
||||
func testEncodeSuccessHistory() throws {
|
||||
// Given
|
||||
let testValue = ["test": "TestDecodable"]
|
||||
let subject = PassthroughSubject<[String: String], Error>()
|
||||
let testValue = ["test" : "TestDecodable"]
|
||||
let subject = PassthroughSubject<[String : String], Error>()
|
||||
let publisher = subject.encode(encoder: encoder)
|
||||
let subscriber = TrackingSubscriberBase<Int, Error>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
|
||||
// When
|
||||
publisher.subscribe(subscriber)
|
||||
subject.send(testValue)
|
||||
|
||||
// Then
|
||||
guard let testKey = encoder.encoded.first?.key, encoder.encoded.count == 1 else {
|
||||
XCTFail("Could not get testing data from encoding")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Encode"),
|
||||
.value(testKey)])
|
||||
}
|
||||
|
||||
func testDemand() {
|
||||
// `CustomSubscription` tracks all the requests and cancellations
|
||||
// in its `history` property
|
||||
let subscription = CustomSubscription()
|
||||
|
||||
// `CustomPublisher` sends the subscription object it has been initialized with
|
||||
// to whoever subscribed to the `CustomPublisher`.
|
||||
let publisher = CustomPublisherBase<[String: String]>(subscription: subscription)
|
||||
let publisher = CustomPublisherBase<[String : String], TestingError>(
|
||||
subscription: subscription
|
||||
)
|
||||
|
||||
// `_Encode` helper will receive the `CustomSubscription `
|
||||
let encode = publisher.encode(encoder: encoder)
|
||||
|
||||
// This is actually `_Decode`
|
||||
var downstreamSubscription: Subscription?
|
||||
|
||||
// `TrackingSubscriber` records every event like "receiveSubscription",
|
||||
// "receiveValue" and "receiveCompletion" into its `history` property,
|
||||
// optionally executing the provided callbacks.
|
||||
let tracking = TrackingSubscriberBase<Int, Error>(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(37))
|
||||
@@ -96,7 +103,9 @@ final class EncodeTests: XCTestCase {
|
||||
)
|
||||
|
||||
encode.subscribe(tracking)
|
||||
XCTAssertNotNil(downstreamSubscription) // Removes unused variable warning
|
||||
XCTAssertNotNil(downstreamSubscription)
|
||||
|
||||
XCTAssertEqual(publisher.send(["test" : "TestDecodable"]), .max(2))
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(37))])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,21 +13,21 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class FailTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSubscription", testSubscription),
|
||||
]
|
||||
|
||||
private typealias Sut = Publishers.Fail<Int, TestingError>
|
||||
private typealias Sut = Fail<Int, TestingError>
|
||||
|
||||
func testSubscription() {
|
||||
let just = Sut(error: .oops)
|
||||
let tracking = TrackingSubscriber()
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Empty"),
|
||||
.completion(.failure(.oops))])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,17 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class JustTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testJustNoInitialDemand", testJustNoInitialDemand),
|
||||
("testCustomMirror", testCustomMirror),
|
||||
("testJustWithInitialDemand", testJustWithInitialDemand),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testCancelOnSubscription", testCancelOnSubscription),
|
||||
("testMinOperatorSpecialization", testMinOperatorSpecialization),
|
||||
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
|
||||
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
|
||||
("testTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
|
||||
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
|
||||
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
|
||||
("testRemoveDuplicatesOperatorSpecialization",
|
||||
@@ -38,24 +37,16 @@ final class JustTests: XCTestCase {
|
||||
("testCountOperatorSpecialization", testCountOperatorSpecialization),
|
||||
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
|
||||
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
|
||||
("testTryDropWhileOperatorSpecialization",
|
||||
testTryDropWhileOperatorSpecialization),
|
||||
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
|
||||
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
|
||||
("testTryFirstWhereOperatorSpecializtion",
|
||||
testTryFirstWhereOperatorSpecializtion),
|
||||
("testLastOperatorSpecialization", testLastOperatorSpecialization),
|
||||
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
|
||||
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
|
||||
("testIgnoreOutputOperatorSpecialization",
|
||||
testIgnoreOutputOperatorSpecialization),
|
||||
("testMapOperatorSpecialization", testMapOperatorSpecialization),
|
||||
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
|
||||
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
|
||||
("testTryCompactMapOperatorSpecialization",
|
||||
testTryCompactMapOperatorSpecialization),
|
||||
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
|
||||
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
|
||||
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
|
||||
("testReplaceErrorOperatorSpecialization",
|
||||
testReplaceErrorOperatorSpecialization),
|
||||
@@ -72,8 +63,6 @@ final class JustTests: XCTestCase {
|
||||
testOutputInRangeOperatorSpecialization),
|
||||
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
|
||||
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
|
||||
("testTryPrefixWhileOperatorSpecialization",
|
||||
testTryPrefixWhileOperatorSpecialization),
|
||||
("testSetFailureTypeOperatorSpecialization",
|
||||
testSetFailureTypeOperatorSpecialization),
|
||||
]
|
||||
@@ -85,16 +74,34 @@ final class JustTests: XCTestCase {
|
||||
let tracking = TrackingSubscriberBase<Int, Never>()
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("Just")])
|
||||
|
||||
tracking.subscriptions.first?.request(.max(100))
|
||||
tracking.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Just"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testCustomMirror() throws {
|
||||
let just = Sut(42)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
just.subscribe(tracking)
|
||||
|
||||
var reflected = ""
|
||||
try dump(XCTUnwrap(downstreamSubscription), to: &reflected)
|
||||
|
||||
XCTAssertEqual(reflected, """
|
||||
▿ Just #0
|
||||
- 42
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
func testJustWithInitialDemand() {
|
||||
let just = Sut(42)
|
||||
let tracking =
|
||||
@@ -103,7 +110,7 @@ final class JustTests: XCTestCase {
|
||||
})
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Just"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
@@ -115,7 +122,7 @@ final class JustTests: XCTestCase {
|
||||
)
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Just"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
@@ -145,22 +152,6 @@ final class JustTests: XCTestCase {
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
|
||||
}
|
||||
|
||||
func testTryMinOperatorSpecialization() {
|
||||
var count = 0
|
||||
let comparator1: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
|
||||
let comparator2: (Int, Int) -> Bool = { count += 1; return $0 != $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut(1).tryMin(by: comparator1).result.get(), true)
|
||||
XCTAssertEqual(try Sut(1).tryMin(by: comparator2).result.get(), false)
|
||||
assertThrowsError(try Sut(1).tryMin(by: throwingComparator).result.get(), .oops)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testMaxOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut(341).max(), Sut(341))
|
||||
|
||||
@@ -171,22 +162,6 @@ final class JustTests: XCTestCase {
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
|
||||
}
|
||||
|
||||
func testTryMaxOperatorSpecialization() {
|
||||
var count = 0
|
||||
let comparator1: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
|
||||
let comparator2: (Int, Int) -> Bool = { count += 1; return $0 != $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut(1).tryMax(by: comparator1).result.get(), true)
|
||||
XCTAssertEqual(try Sut(1).tryMax(by: comparator2).result.get(), false)
|
||||
assertThrowsError(try Sut(1).tryMax(by: throwingComparator).result.get(), .oops)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testContainsOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut(10).contains(12), Sut(false))
|
||||
XCTAssertEqual(Sut(10).contains(10), Sut(true))
|
||||
@@ -250,22 +225,16 @@ final class JustTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDropFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(100), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(0), .init(10000))
|
||||
}
|
||||
|
||||
func testDropWhileOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(42).drop { $0 != 42 }.result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).drop { $0 != 42 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).drop { $0 < 0 }.result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(1).drop { $0 > 0 }.result, .success(nil))
|
||||
}
|
||||
|
||||
func testTryDropWhileOperatorSpecialization() {
|
||||
XCTAssertEqual(try Sut<Int>(42).tryDrop { $0 != 42 }.result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryDrop { $0 != 42 }.result.get())
|
||||
assertThrowsError(try Sut<Int>(-13).tryDrop(while: throwing).result.get(), .oops)
|
||||
XCTAssertEqual(Sut<Int>(42).drop { $0 != 42 }, .init(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).drop { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).drop { $0 < 0 }, .init(1))
|
||||
XCTAssertEqual(Sut<Int>(1).drop { $0 > 0 }, .init(nil))
|
||||
}
|
||||
|
||||
func testFirstOperatorSpecialization() {
|
||||
@@ -273,16 +242,10 @@ final class JustTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(Sut<Int>(42).first { $0 != 42 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).first { $0 != 42 }.result, .success(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).first { $0 < 0 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).first { $0 > 0 }.result, .success(1))
|
||||
}
|
||||
|
||||
func testTryFirstWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(try Sut<Int>(-13).tryFirst { $0 != 42 }.result.get(), -13)
|
||||
XCTAssertNil(try Sut<Int>(42).tryFirst { $0 != 42 }.result.get())
|
||||
assertThrowsError(try Sut<Int>(-13).tryFirst(where: throwing).result.get(), .oops)
|
||||
XCTAssertEqual(Sut<Int>(42).first { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).first { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).first { $0 < 0 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).first { $0 > 0 }, .init(1))
|
||||
}
|
||||
|
||||
func testLastOperatorSpecialization() {
|
||||
@@ -290,16 +253,10 @@ final class JustTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(Sut<Int>(42).last { $0 != 42 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).last { $0 != 42 }.result, .success(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).last { $0 < 0 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).last { $0 > 0 }.result, .success(1))
|
||||
}
|
||||
|
||||
func testTryLastWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(try Sut<Int>(-13).tryLast { $0 != 42 }.result.get(), -13)
|
||||
XCTAssertNil(try Sut<Int>(42).tryLast { $0 != 42 }.result.get())
|
||||
assertThrowsError(try Sut<Int>(-13).tryLast(where: throwing).result.get(), .oops)
|
||||
XCTAssertEqual(Sut<Int>(42).last { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).last { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).last { $0 < 0 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).last { $0 > 0 }, .init(1))
|
||||
}
|
||||
|
||||
func testIgnoreOutputOperatorSpecialization() {
|
||||
@@ -318,30 +275,15 @@ final class JustTests: XCTestCase {
|
||||
func testCompactMapOperatorSpecialization() {
|
||||
let transform: (Int) -> String? = { $0 == 42 ? String($0) : nil }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
|
||||
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
|
||||
}
|
||||
|
||||
func testTryCompactMapOperatorSpecialization() {
|
||||
let transform: (Int) -> String? = { $0 == 42 ? String($0) : nil }
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
|
||||
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
|
||||
assertThrowsError(try Sut<Int>(42).tryMap(throwing).result.get() as Int?, "oops")
|
||||
XCTAssertEqual(Sut<Int>(42).compactMap(transform), .init("42"))
|
||||
XCTAssertEqual(Sut<Int>(100).compactMap(transform), .init(nil))
|
||||
}
|
||||
|
||||
func testFilterOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(42).filter { $0 != 42 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).filter { $0 != 42 }.result, .success(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).filter { $0 < 0 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).filter { $0 > 0 }.result, .success(1))
|
||||
}
|
||||
|
||||
func testTryFilterOperatorSpecialization() {
|
||||
XCTAssertEqual(try Sut<Int>(-13).tryFilter { $0 != 42 }.result.get(), -13)
|
||||
XCTAssertNil(try Sut<Int>(42).tryFilter { $0 != 42 }.result.get())
|
||||
assertThrowsError(try Sut<Int>(-13).tryFilter(throwing).result.get() as Int?,
|
||||
.oops)
|
||||
XCTAssertEqual(Sut<Int>(42).filter { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).filter { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).filter { $0 < 0 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).filter { $0 > 0 }, .init(1))
|
||||
}
|
||||
|
||||
func testMapErrorOperatorSpecialization() {
|
||||
@@ -357,7 +299,6 @@ final class JustTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRetryOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut(1).retry(), Sut(1))
|
||||
XCTAssertEqual(Sut(1).retry(Int.max), Sut(1))
|
||||
}
|
||||
|
||||
@@ -380,9 +321,9 @@ final class JustTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testOutputAtIndexOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 0), .init(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 1), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 42), .init(nil))
|
||||
}
|
||||
|
||||
func testOutputInRangeOperatorSpecialization() {
|
||||
@@ -390,34 +331,27 @@ final class JustTests: XCTestCase {
|
||||
// Empty range should result in a nil
|
||||
// If this is fixed, this test will fail in compatibility mode so we will need
|
||||
// to change our implementation to the correct one.
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10), .init(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0), .init(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0), .init(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10), .init(nil))
|
||||
}
|
||||
|
||||
func testPrefixOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(0), .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1), .init(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1000), .init(98))
|
||||
}
|
||||
|
||||
func testPrefixWhileOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(42).prefix { $0 != 42 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).prefix { $0 != 42 }.result, .success(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).prefix { $0 < 0 }.result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).prefix { $0 > 0 }.result, .success(1))
|
||||
}
|
||||
|
||||
func testTryPrefixWhileOperatorSpecialization() {
|
||||
XCTAssertEqual(try Sut<Int>(-13).tryPrefix { $0 != 42 }.result.get(), -13)
|
||||
XCTAssertNil(try Sut<Int>(42).tryPrefix { $0 != 42 }.result.get())
|
||||
assertThrowsError(try Sut<Int>(-13).tryPrefix(while: throwing).result.get(),
|
||||
.oops)
|
||||
XCTAssertEqual(Sut<Int>(42).prefix { $0 != 42 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(-13).prefix { $0 != 42 }, .init(-13))
|
||||
XCTAssertEqual(Sut<Int>(1).prefix { $0 < 0 }, .init(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).prefix { $0 > 0 }, .init(1))
|
||||
}
|
||||
|
||||
func testSetFailureTypeOperatorSpecialization() {
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
//
|
||||
// MapErrorTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class MapErrorTests: XCTestCase {
|
||||
static let allTests = [
|
||||
("testEmpty", testEmpty),
|
||||
("testError", testError),
|
||||
("testRange", testRange),
|
||||
("testNoDemand", testNoDemand),
|
||||
("testDemandSubscribe", testDemandSubscribe),
|
||||
("testDemandSend", testDemandSend),
|
||||
("testCompletion", testCompletion),
|
||||
("testCancel", testCancel),
|
||||
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
|
||||
("testLifecycle", testLifecycle),
|
||||
]
|
||||
|
||||
func testEmpty() {
|
||||
// Given
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
let publisher = TrackingSubject<Int>(
|
||||
receiveSubscriber: {
|
||||
XCTAssertEqual(String(describing: $0), "MapError")
|
||||
}
|
||||
)
|
||||
// When
|
||||
publisher.mapError(OtherError.init).subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")])
|
||||
}
|
||||
|
||||
func testError() {
|
||||
// Given
|
||||
let expectedError = TestingError.oops
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
// When
|
||||
publisher.mapError(OtherError.init).subscribe(tracking)
|
||||
publisher.send(completion: .failure(expectedError))
|
||||
publisher.send(completion: .failure(expectedError))
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [
|
||||
.subscription("CustomSubscription"),
|
||||
.completion(.failure(OtherError(expectedError))),
|
||||
.completion(.failure(OtherError(expectedError)))
|
||||
])
|
||||
}
|
||||
|
||||
func testRange() {
|
||||
// Given
|
||||
let publisher = PassthroughSubject<Int, TestingError>()
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
// When
|
||||
publisher.send(1)
|
||||
mapError.subscribe(tracking)
|
||||
publisher.send(2)
|
||||
publisher.send(3)
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(5)
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [
|
||||
.subscription("PassthroughSubject"),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.finished)
|
||||
])
|
||||
}
|
||||
|
||||
func testNoDemand() {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>()
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertTrue(subscription.history.isEmpty)
|
||||
}
|
||||
|
||||
func testDemandSubscribe() {
|
||||
// Given
|
||||
let expectedSubscribeDemand = 42
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { $0.request(.max(expectedSubscribeDemand)) }
|
||||
)
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(expectedSubscribeDemand))])
|
||||
}
|
||||
|
||||
func testDemandSend() {
|
||||
// Given
|
||||
let expectedReceiveValueDemand = 4
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveValue: { _ in .max(expectedReceiveValueDemand) }
|
||||
)
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertEqual(publisher.send(0), .max(expectedReceiveValueDemand))
|
||||
}
|
||||
|
||||
func testCompletion() {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
publisher.send(completion: .finished)
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(
|
||||
tracking.history,
|
||||
[.subscription("CustomSubscription"), .completion(.finished)]
|
||||
)
|
||||
}
|
||||
|
||||
func testCancel() throws {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
}
|
||||
)
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let mapError = publisher.mapError(OtherError.init)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
}
|
||||
)
|
||||
// When
|
||||
mapError.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
downstreamSubscription?.request(.unlimited)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.requested(.unlimited),
|
||||
.cancelled])
|
||||
}
|
||||
|
||||
func testLifecycle() throws {
|
||||
|
||||
var deinitCounter = 0
|
||||
|
||||
let onDeinit = { deinitCounter += 1 }
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let mapError = passthrough.mapError(OtherError.init)
|
||||
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
mapError.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
passthrough.send(31)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 0)
|
||||
passthrough.send(completion: .failure("failure"))
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 1)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let mapError = passthrough.mapError(OtherError.init)
|
||||
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
mapError.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 0)
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
let mapError = passthrough.mapError(OtherError.init)
|
||||
let emptySubscriber = TrackingSubscriberBase<Int, OtherError>(
|
||||
receiveSubscription: { subscription = $0; $0.request(.unlimited) },
|
||||
onDeinit: onDeinit
|
||||
)
|
||||
XCTAssertTrue(emptySubscriber.history.isEmpty)
|
||||
mapError.subscribe(emptySubscriber)
|
||||
XCTAssertEqual(emptySubscriber.subscriptions.count, 1)
|
||||
passthrough.send(31)
|
||||
XCTAssertEqual(emptySubscriber.inputs.count, 1)
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
}
|
||||
|
||||
private struct OtherError: Error {
|
||||
let original: Error
|
||||
|
||||
init(_ original: Error) {
|
||||
self.original = original
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,21 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class MapTests: XCTestCase {
|
||||
static let allTests = [
|
||||
("testEmpty", testEmpty),
|
||||
("testError", testError),
|
||||
("testTryMapFailureBecauseOfThrow", testTryMapFailureBecauseOfThrow),
|
||||
("testTryMapFailureOnCompletion", testTryMapFailureOnCompletion),
|
||||
("testTryMapSuccess", testTryMapSuccess),
|
||||
("testRange", testRange),
|
||||
("testNoDemand", testNoDemand),
|
||||
("testDemandSubscribe", testDemandSubscribe),
|
||||
("testDemandSend", testDemandSend),
|
||||
("testCompletion", testCompletion),
|
||||
("testCancel", testCancel),
|
||||
("testMapCancel", testMapCancel),
|
||||
("testTryMapCancel", testTryMapCancel),
|
||||
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testMapOperatorSpecializationForMap", testMapOperatorSpecializationForMap),
|
||||
@@ -39,28 +41,34 @@ final class MapTests: XCTestCase {
|
||||
|
||||
func testEmpty() {
|
||||
// Given
|
||||
let tracking = TrackingSubscriberBase<Int, Never>(
|
||||
let tracking = TrackingSubscriberBase<String, TestingError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
let publisher = PassthroughSubject<Int, Never>()
|
||||
let publisher = TrackingSubject<Int>(
|
||||
receiveSubscriber: {
|
||||
XCTAssertEqual(String(describing: $0), "Map")
|
||||
}
|
||||
)
|
||||
// When
|
||||
publisher.map { $0 * 2 }.subscribe(tracking)
|
||||
publisher.map(String.init).subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")])
|
||||
}
|
||||
|
||||
func testError() {
|
||||
// Given
|
||||
let expectedError = TestingError.oops
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
let publisher = PassthroughSubject<Int, TestingError>()
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
// When
|
||||
publisher.map { $0 * 2 }.subscribe(tracking)
|
||||
publisher.send(completion: .failure(expectedError))
|
||||
publisher.send(completion: .failure(expectedError))
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [
|
||||
.subscription(Subscriptions.empty),
|
||||
.completion(Subscribers.Completion<TestingError>.failure(expectedError))
|
||||
.subscription("CustomSubscription"),
|
||||
.completion(.failure(expectedError)),
|
||||
.completion(.failure(expectedError))
|
||||
])
|
||||
}
|
||||
|
||||
@@ -88,7 +96,7 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription(Subscriptions.empty),
|
||||
[.subscription("TryMap"),
|
||||
.value(4),
|
||||
.value(6),
|
||||
.completion(.failure("too much" as TestingError))])
|
||||
@@ -109,10 +117,26 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(2)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription(Subscriptions.empty),
|
||||
[.subscription("TryMap"),
|
||||
.completion(.failure(TestingError.oops))])
|
||||
}
|
||||
|
||||
func testTryMapSuccess() {
|
||||
let publisher = PassthroughSubject<Int, Error>()
|
||||
let map = publisher.tryMap { $0 * 2 }
|
||||
|
||||
let tracking = TrackingSubscriberBase<Int, Error>()
|
||||
|
||||
publisher.send(1)
|
||||
map.subscribe(tracking)
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(2)
|
||||
|
||||
XCTAssertEqual(tracking.history,
|
||||
[.subscription("TryMap"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testRange() {
|
||||
// Given
|
||||
let publisher = PassthroughSubject<Int, TestingError>()
|
||||
@@ -127,7 +151,7 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(5)
|
||||
// Then
|
||||
XCTAssertEqual(tracking.history, [
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription("PassthroughSubject"),
|
||||
.value(4),
|
||||
.value(6),
|
||||
.completion(.finished)
|
||||
@@ -162,18 +186,22 @@ final class MapTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDemandSend() {
|
||||
// Given
|
||||
let expectedReceiveValueDemand = 4
|
||||
var expectedReceiveValueDemand = 4
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let map = publisher.map { $0 * 2 }
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) },
|
||||
receiveValue: { _ in .max(expectedReceiveValueDemand) }
|
||||
)
|
||||
// When
|
||||
|
||||
map.subscribe(tracking)
|
||||
// Then
|
||||
XCTAssertEqual(publisher.send(0), .max(expectedReceiveValueDemand))
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .max(4))
|
||||
|
||||
expectedReceiveValueDemand = 120
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .max(120))
|
||||
}
|
||||
|
||||
func testCompletion() {
|
||||
@@ -189,11 +217,11 @@ final class MapTests: XCTestCase {
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(
|
||||
tracking.history,
|
||||
[.subscription(Subscriptions.empty), .completion(.finished)]
|
||||
[.subscription("CustomSubscription"), .completion(.finished)]
|
||||
)
|
||||
}
|
||||
|
||||
func testCancel() throws {
|
||||
func testMapCancel() throws {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
@@ -206,6 +234,27 @@ final class MapTests: XCTestCase {
|
||||
// When
|
||||
map.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
_ = publisher.send(1)
|
||||
publisher.send(completion: .finished)
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
}
|
||||
|
||||
func testTryMapCancel() throws {
|
||||
// Given
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
let map = publisher.tryMap { $0 * 2 }
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriberBase<Int, Error>(receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
})
|
||||
// When
|
||||
map.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
_ = publisher.send(1)
|
||||
publisher.send(completion: .finished)
|
||||
// Then
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
}
|
||||
@@ -251,7 +300,7 @@ final class MapTests: XCTestCase {
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 1)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
do {
|
||||
let passthrough = PassthroughSubject<Int, TestingError>()
|
||||
@@ -264,7 +313,7 @@ final class MapTests: XCTestCase {
|
||||
XCTAssertEqual(emptySubscriber.completions.count, 0)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
|
||||
var subscription: Subscription?
|
||||
|
||||
@@ -284,9 +333,9 @@ final class MapTests: XCTestCase {
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 1)
|
||||
try XCTUnwrap(subscription).cancel()
|
||||
XCTAssertEqual(deinitCounter, 0)
|
||||
XCTAssertEqual(deinitCounter, 2)
|
||||
}
|
||||
|
||||
func testMapOperatorSpecializationForMap() {
|
||||
@@ -305,7 +354,7 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(5)
|
||||
|
||||
XCTAssert(map1.upstream === map2.upstream)
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11)])
|
||||
@@ -330,14 +379,14 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(5)
|
||||
|
||||
XCTAssert(map1.upstream === tryMap2.upstream)
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11)])
|
||||
|
||||
publisher.send(6)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11),
|
||||
@@ -363,14 +412,14 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(5)
|
||||
|
||||
XCTAssert(tryMap1.upstream === tryMap2.upstream)
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11)])
|
||||
|
||||
publisher.send(6)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11),
|
||||
@@ -396,14 +445,14 @@ final class MapTests: XCTestCase {
|
||||
publisher.send(5)
|
||||
|
||||
XCTAssert(tryMap1.upstream === tryMap2.upstream)
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11)])
|
||||
|
||||
publisher.send(6)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("TryMap"),
|
||||
.value(5),
|
||||
.value(7),
|
||||
.value(11),
|
||||
|
||||
@@ -13,45 +13,48 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class MulticastTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testMulticast", testMulticast),
|
||||
("testMulticastConnectTwice", testMulticastConnectTwice),
|
||||
("testMulticastDisconnect", testMulticastDisconnect),
|
||||
("testLateSubscriber", testLateSubscriber),
|
||||
("testSubscribeAfterCompletion", testSubscribeAfterCompletion),
|
||||
]
|
||||
|
||||
func testMulticast() throws {
|
||||
|
||||
let publisher = PassthroughSubject<Int, TestingError>()
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
let multicast = publisher.multicast(PassthroughSubject.init)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
|
||||
multicast.subscribe(tracking)
|
||||
|
||||
publisher.send(0)
|
||||
publisher.send(12)
|
||||
XCTAssertEqual(publisher.send(0), .none)
|
||||
XCTAssertEqual(publisher.send(12), .none)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("Multicast")])
|
||||
|
||||
var connection = multicast.connect()
|
||||
|
||||
publisher.send(-1)
|
||||
publisher.send(42)
|
||||
XCTAssertEqual(publisher.send(-1), .none)
|
||||
XCTAssertEqual(publisher.send(42), .none)
|
||||
|
||||
connection.cancel()
|
||||
|
||||
publisher.send(14)
|
||||
XCTAssertEqual(publisher.send(14), .none)
|
||||
|
||||
connection = multicast.connect()
|
||||
|
||||
publisher.send(15)
|
||||
XCTAssertEqual(publisher.send(15), .none)
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
connection.cancel()
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
|
||||
.value(-1),
|
||||
.value(42),
|
||||
.value(15),
|
||||
@@ -60,9 +63,12 @@ final class MulticastTests: XCTestCase {
|
||||
|
||||
func testMulticastConnectTwice() {
|
||||
|
||||
let publisher = PassthroughSubject<Int, TestingError>()
|
||||
let multicast = publisher.multicast(PassthroughSubject.init)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
let publisher = TrackingSubject<Int>()
|
||||
let multicastSubject = TrackingSubject<Int>()
|
||||
let multicast = publisher.multicast(subject: multicastSubject)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(10)) }
|
||||
)
|
||||
|
||||
multicast.subscribe(tracking)
|
||||
|
||||
@@ -74,7 +80,7 @@ final class MulticastTests: XCTestCase {
|
||||
publisher.send(42)
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
|
||||
.value(42),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
@@ -104,9 +110,9 @@ final class MulticastTests: XCTestCase {
|
||||
publisher.send(2)
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Multicast"),
|
||||
.value(42),
|
||||
.subscription(Subscriptions.empty),
|
||||
.subscription("Multicast"),
|
||||
.value(2),
|
||||
.value(2),
|
||||
.completion(.finished),
|
||||
@@ -114,4 +120,169 @@ final class MulticastTests: XCTestCase {
|
||||
|
||||
connection.cancel()
|
||||
}
|
||||
|
||||
func testLateSubscriber() {
|
||||
let subscription = CustomSubscription()
|
||||
|
||||
let publisher = CustomPublisher(subscription: subscription)
|
||||
|
||||
let subject = TrackingSubject<Int>()
|
||||
|
||||
let multicast = publisher.multicast(subject: subject)
|
||||
|
||||
XCTAssert(subject.history.isEmpty)
|
||||
|
||||
let earlySubscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(3))
|
||||
}
|
||||
)
|
||||
|
||||
multicast.subscribe(earlySubscriber)
|
||||
|
||||
XCTAssertNil(publisher.subscriber)
|
||||
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast")])
|
||||
|
||||
XCTAssertEqual(subject.history, [.subscriber])
|
||||
|
||||
let connection = multicast.connect()
|
||||
|
||||
XCTAssert(connection is AnyCancellable)
|
||||
XCTAssertNotNil(publisher.subscriber)
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast")])
|
||||
XCTAssertEqual(subject.history, [.subscriber, .subscription("Subject")])
|
||||
|
||||
XCTAssertEqual(publisher.send(1), .none)
|
||||
XCTAssertEqual(publisher.send(2), .none)
|
||||
XCTAssertEqual(publisher.send(3), .none)
|
||||
XCTAssertEqual(publisher.send(4), .none)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
XCTAssertEqual(subject.history, [.subscriber,
|
||||
.subscription("Subject"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4)])
|
||||
|
||||
let lateSubscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.max(2))
|
||||
}
|
||||
)
|
||||
|
||||
multicast.subscribe(lateSubscriber)
|
||||
|
||||
XCTAssertEqual(publisher.send(5), .none)
|
||||
XCTAssertEqual(publisher.send(6), .none)
|
||||
XCTAssertEqual(publisher.send(7), .none)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast"),
|
||||
.value(5),
|
||||
.value(6)])
|
||||
XCTAssertEqual(subject.history, [.subscriber,
|
||||
.subscription("Subject"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.subscriber,
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7)])
|
||||
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
let latestSubscriber = TrackingSubscriber(
|
||||
receiveSubscription: {
|
||||
$0.request(.none)
|
||||
}
|
||||
)
|
||||
|
||||
multicast.subscribe(latestSubscriber)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(earlySubscriber.history, [.subscription("Multicast"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast"),
|
||||
.value(5),
|
||||
.value(6),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(latestSubscriber.history, [.subscription("Multicast"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(subject.history, [.subscriber,
|
||||
.subscription("Subject"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.value(4),
|
||||
.subscriber,
|
||||
.value(5),
|
||||
.value(6),
|
||||
.value(7),
|
||||
.completion(.finished),
|
||||
.subscriber])
|
||||
}
|
||||
|
||||
func testSubscribeAfterCompletion() {
|
||||
|
||||
final class Subj: Subject {
|
||||
|
||||
typealias Output = Int
|
||||
|
||||
typealias Failure = TestingError
|
||||
|
||||
var subscriber: AnySubscriber<Int, TestingError>?
|
||||
|
||||
func receive<Downstream: Subscriber>(subscriber: Downstream)
|
||||
where Downstream.Failure == TestingError, Downstream.Input == Int
|
||||
{
|
||||
subscriber.receive(subscription: CustomSubscription())
|
||||
self.subscriber = AnySubscriber(subscriber)
|
||||
}
|
||||
|
||||
func send(subscription: Subscription) {
|
||||
subscriber?.receive(subscription: subscription)
|
||||
}
|
||||
|
||||
func send(_ value: Int) {
|
||||
_ = subscriber?.receive(value)
|
||||
}
|
||||
|
||||
func send(completion: Subscribers.Completion<TestingError>) {
|
||||
subscriber?.receive(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
let publisher = CustomPublisher(subscription: CustomSubscription())
|
||||
|
||||
let subject = Subj()
|
||||
|
||||
let multicast = publisher.multicast(subject: subject)
|
||||
|
||||
_ = multicast.connect()
|
||||
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
let lateSubscriber = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
|
||||
multicast.subscribe(lateSubscriber)
|
||||
|
||||
XCTAssertEqual(lateSubscriber.history, [.subscription("Multicast")])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
//
|
||||
// OptionalPublisherTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18.06.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class OptionalPublisherTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSuccessNoInitialDemand", testSuccessNoInitialDemand),
|
||||
("testSuccessWithInitialDemand", testSuccessWithInitialDemand),
|
||||
("testSuccessCancelOnSubscription", testSuccessCancelOnSubscription),
|
||||
("testNil", testNil),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testMinOperatorSpecialization", testMinOperatorSpecialization),
|
||||
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
|
||||
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
|
||||
("testRemoveDuplicatesOperatorSpecialization",
|
||||
testRemoveDuplicatesOperatorSpecialization),
|
||||
("testAllSatifyOperatorSpecialization", testAllSatifyOperatorSpecialization),
|
||||
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
|
||||
("testCountOperatorSpecialization", testCountOperatorSpecialization),
|
||||
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
|
||||
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
|
||||
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
|
||||
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
|
||||
("testLastOperatorSpecialization", testLastOperatorSpecialization),
|
||||
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
|
||||
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
|
||||
("testIgnoreOutputOperatorSpecialization",
|
||||
testIgnoreOutputOperatorSpecialization),
|
||||
("testMapOperatorSpecialization", testMapOperatorSpecialization),
|
||||
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
|
||||
("testReplaceErrorOperatorSpecialization",
|
||||
testReplaceErrorOperatorSpecialization),
|
||||
("testReplaceEmptyOperatorSpecialization",
|
||||
testReplaceEmptyOperatorSpecialization),
|
||||
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
|
||||
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
|
||||
("testScanOperatorSpecialization", testScanOperatorSpecialization),
|
||||
("testOutputAtIndexOperatorSpecialization",
|
||||
testOutputAtIndexOperatorSpecialization),
|
||||
("testOutputInRangeOperatorSpecialization",
|
||||
testOutputInRangeOperatorSpecialization),
|
||||
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
|
||||
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
|
||||
]
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
private typealias Sut<Output> = Optional<Output>.Publisher
|
||||
#else
|
||||
private typealias Sut<Output> = Optional<Output>.OCombine.Publisher
|
||||
#endif
|
||||
|
||||
func testSuccessNoInitialDemand() {
|
||||
let success = Sut(42)
|
||||
let tracking = TrackingSubscriberBase<Int, Never>()
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Optional")])
|
||||
|
||||
tracking.subscriptions.first?.request(.max(100))
|
||||
tracking.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Optional"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSuccessWithInitialDemand() {
|
||||
let just = Sut(42)
|
||||
let tracking = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Optional"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSuccessCancelOnSubscription() {
|
||||
let success = Sut(42)
|
||||
let tracking = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
|
||||
)
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Optional"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testNil() {
|
||||
let success = Sut<Int>(nil)
|
||||
let tracking = TrackingSubscriberBase<Int, Never>()
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testLifecycle() {
|
||||
var deinitCount = 0
|
||||
do {
|
||||
let once = Sut(42)
|
||||
let tracking = TrackingSubscriberBase<Int, Never>(
|
||||
onDeinit: { deinitCount += 1 }
|
||||
)
|
||||
once.subscribe(tracking)
|
||||
tracking.subscriptions.first?.cancel()
|
||||
}
|
||||
XCTAssertEqual(deinitCount, 1)
|
||||
}
|
||||
|
||||
// MARK: - Operator specializations for Optional
|
||||
|
||||
func testMinOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(112).min(), Sut(112))
|
||||
XCTAssertEqual(Sut<Int>(nil).min(), Sut(nil))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
XCTAssertEqual(Sut<Int>(1).min(by: comparator), Sut(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).min(by: comparator), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
|
||||
}
|
||||
|
||||
func testMaxOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(341).max(), Sut(341))
|
||||
XCTAssertEqual(Sut<Int>(nil).max(), Sut(nil))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(2).max(by: comparator), Sut(2))
|
||||
XCTAssertEqual(Sut<Int>(nil).max(by: comparator), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
|
||||
}
|
||||
|
||||
func testContainsOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10).contains(12), Sut(false))
|
||||
XCTAssertEqual(Sut<Int>(10).contains(10), Sut(true))
|
||||
XCTAssertEqual(Sut<Int>(nil).contains(10), Sut(nil))
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 < 100 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(64).contains(where: predicate), Sut(true))
|
||||
XCTAssertEqual(Sut<Int>(112).contains(where: predicate), Sut(false))
|
||||
XCTAssertEqual(Sut<Int>(nil).contains(where: predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testRemoveDuplicatesOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1000).removeDuplicates(), Sut(1000))
|
||||
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(), Sut(nil))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(44).removeDuplicates(by: comparator), Sut(44))
|
||||
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(by: comparator), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count,
|
||||
0,
|
||||
"comparator should not be called for removeDuplicates(by:)")
|
||||
}
|
||||
|
||||
func testAllSatifyOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(0).allSatisfy(predicate), Sut(false))
|
||||
XCTAssertEqual(Sut<Int>(1).allSatisfy(predicate), Sut(true))
|
||||
XCTAssertEqual(Sut<Int>(nil).allSatisfy(predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testCollectOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(13).collect(), Sut([13]))
|
||||
XCTAssertEqual(Sut<Int>(nil).collect(), Sut(nil))
|
||||
}
|
||||
|
||||
func testCountOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).count(), Sut(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).count(), Sut(nil))
|
||||
}
|
||||
|
||||
func testDropFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(100), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(0), Sut(10000))
|
||||
XCTAssertEqual(Sut<Int>(nil).dropFirst(), Sut(nil))
|
||||
}
|
||||
|
||||
func testDropWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).drop(while: predicate), Sut(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).drop(while: predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(3).first(), Sut(3))
|
||||
XCTAssertEqual(Sut<Int>(nil).first(), Sut(nil))
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).first(where: predicate), Sut(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).first(where: predicate), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).first(where: predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testLastOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(4).last(), Sut(4))
|
||||
XCTAssertEqual(Sut<Int>(nil).last(), Sut(nil))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).last(where: predicate), Sut(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).last(where: predicate), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).last(where: predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testFilterOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).filter(predicate), Sut(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).filter(predicate), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).filter(predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testIgnoreOutputOperatorSpecialization() {
|
||||
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
|
||||
}
|
||||
|
||||
func testMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String = { count += 1; return String($0) }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).map(transform), Sut("42"))
|
||||
XCTAssertEqual(Sut<Int>(nil).map(transform), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testCompactMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String? = {
|
||||
count += 1
|
||||
return $0 == 42 ? String($0) : nil
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).compactMap(transform), Sut("42"))
|
||||
XCTAssertEqual(Sut<Int>(100).compactMap(transform), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).compactMap(transform), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testReplaceErrorOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).replaceError(with: 100), Sut(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).replaceError(with: 100), Sut(nil))
|
||||
}
|
||||
|
||||
func testReplaceEmptyOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).replaceEmpty(with: 100), Just(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).replaceEmpty(with: 100), Just(100))
|
||||
}
|
||||
|
||||
func testRetryOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).retry(100), Sut(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).retry(100), Sut(nil))
|
||||
}
|
||||
|
||||
func testReduceOperatorSpecialization() {
|
||||
var count = 0
|
||||
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(4).reduce(2, plus), Sut(6))
|
||||
XCTAssertEqual(Sut<Int>(nil).reduce(2, plus), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testScanOperatorSpecialization() {
|
||||
var count = 0
|
||||
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(4).scan(2, plus), Sut(6))
|
||||
XCTAssertEqual(Sut<Int>(nil).scan(2, plus), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testOutputAtIndexOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 0), Sut(12))
|
||||
XCTAssertEqual(Sut<Int>(nil).output(at: 0), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 1), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 42), Sut(nil))
|
||||
}
|
||||
|
||||
func testOutputInRangeOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10), Sut(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< (.max - 2)), Sut(12))
|
||||
XCTAssertEqual(Sut<Int>(nil).output(in: 0 ..< 10), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0), Sut(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: ...0), Sut(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: ..<0), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: ..<1), Sut(12))
|
||||
|
||||
let trackingRange = TrackingRangeExpression(0 ..< 10)
|
||||
_ = Sut<Int>(12).output(in: trackingRange)
|
||||
XCTAssertEqual(trackingRange.history, [.relativeTo(0 ..< .max)])
|
||||
}
|
||||
|
||||
func testPrefixOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(0), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1), Sut(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1000), Sut(98))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(0), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(1), Sut(nil))
|
||||
}
|
||||
|
||||
func testPrefixWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate), Sut(98))
|
||||
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate), Sut(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(while: predicate), Sut(nil))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
}
|
||||
@@ -1,814 +0,0 @@
|
||||
//
|
||||
// OptionalTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 18.06.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class OptionalTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testSuccessNoInitialDemand", testSuccessNoInitialDemand),
|
||||
("testSuccessWithInitialDemand", testSuccessWithInitialDemand),
|
||||
("testSuccessCancelOnSubscription", testSuccessCancelOnSubscription),
|
||||
("testFailure", testFailure),
|
||||
("testFailureCancelOnSubscription", testFailureCancelOnSubscription),
|
||||
("testNil", testNil),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testMinOperatorSpecialization", testMinOperatorSpecialization),
|
||||
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
|
||||
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
|
||||
("testTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
|
||||
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
|
||||
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
|
||||
("testRemoveDuplicatesOperatorSpecialization",
|
||||
testRemoveDuplicatesOperatorSpecialization),
|
||||
("testTryRemoveDuplicatesOperatorSpecialization",
|
||||
testTryRemoveDuplicatesOperatorSpecialization),
|
||||
("testAllSatifyOperatorSpecialization", testAllSatifyOperatorSpecialization),
|
||||
("testTryAllSatifyOperatorSpecialization",
|
||||
testTryAllSatifyOperatorSpecialization),
|
||||
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
|
||||
("testCountOperatorSpecialization", testCountOperatorSpecialization),
|
||||
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
|
||||
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
|
||||
("testTryDropWhereOperatorSpecialization",
|
||||
testTryDropWhileOperatorSpecialization),
|
||||
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
|
||||
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
|
||||
("testTryFirstWhereOperatorSpecializtion",
|
||||
testTryFirstWhereOperatorSpecializtion),
|
||||
("testLastOperatorSpecialization", testLastOperatorSpecialization),
|
||||
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
|
||||
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
|
||||
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
|
||||
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
|
||||
("testIgnoreOutputOperatorSpecialization",
|
||||
testIgnoreOutputOperatorSpecialization),
|
||||
("testMapOperatorSpecialization", testMapOperatorSpecialization),
|
||||
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
|
||||
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
|
||||
("testTryCompactMapOperatorSpecialization",
|
||||
testTryCompactMapOperatorSpecialization),
|
||||
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
|
||||
("testReplaceErrorOperatorSpecialization",
|
||||
testReplaceErrorOperatorSpecialization),
|
||||
("testReplaceEmptyOperatorSpecialization",
|
||||
testReplaceEmptyOperatorSpecialization),
|
||||
("testRetryOperatorSpecialization", testRetryOperatorSpecialization),
|
||||
("testReduceOperatorSpecialization", testReduceOperatorSpecialization),
|
||||
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
|
||||
("testScanOperatorSpecialization", testScanOperatorSpecialization),
|
||||
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
|
||||
("testOutputAtIndexOperatorSpecialization",
|
||||
testOutputAtIndexOperatorSpecialization),
|
||||
("testOutputInRangeOperatorSpecialization",
|
||||
testOutputInRangeOperatorSpecialization),
|
||||
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
|
||||
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
|
||||
("testTryPrefixWhileOperatorSpecialization",
|
||||
testTryPrefixWhileOperatorSpecialization),
|
||||
("testSetFailureTypeOperatorSpecialization",
|
||||
testSetFailureTypeOperatorSpecialization),
|
||||
]
|
||||
|
||||
private typealias Sut<Output> = Publishers.Optional<Output, TestingError>
|
||||
|
||||
func testSuccessNoInitialDemand() {
|
||||
let success = Sut(42)
|
||||
let tracking = TrackingSubscriber()
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
|
||||
|
||||
tracking.subscriptions.first?.request(.max(100))
|
||||
tracking.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSuccessWithInitialDemand() {
|
||||
let just = Sut(42)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSuccessCancelOnSubscription() {
|
||||
let success = Sut(42)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
|
||||
)
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testFailure() {
|
||||
let failure = Sut<Int>("failure")
|
||||
let tracking = TrackingSubscriber()
|
||||
failure.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.completion(.failure("failure"))])
|
||||
}
|
||||
|
||||
func testFailureCancelOnSubscription() {
|
||||
let failure = Sut<Int>("failure")
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.cancel() }
|
||||
)
|
||||
failure.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.completion(.failure("failure"))])
|
||||
}
|
||||
|
||||
func testNil() {
|
||||
let success = Sut<Int>(nil)
|
||||
let tracking = TrackingSubscriber()
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testLifecycle() {
|
||||
var deinitCount = 0
|
||||
do {
|
||||
let once = Sut(42)
|
||||
let tracking = TrackingSubscriber(onDeinit: { deinitCount += 1 })
|
||||
once.subscribe(tracking)
|
||||
tracking.subscriptions.first?.cancel()
|
||||
}
|
||||
XCTAssertEqual(deinitCount, 1)
|
||||
}
|
||||
|
||||
// MARK: - Operator specializations for Optional
|
||||
|
||||
func testMinOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(112).min().result, .success(112))
|
||||
XCTAssertEqual(Sut<Int>(nil).min().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").min().result, .failure("error"))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
XCTAssertEqual(Sut<Int>(1).min(by: comparator).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).min(by: comparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").min(by: comparator).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for min(by:)")
|
||||
}
|
||||
|
||||
func testTryMinOperatorSpecialization() {
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(1).tryMin(by: comparator).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).tryMin(by: comparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(1).tryMin(by: throwingComparator).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).tryMin(by: throwingComparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").tryMin(by: throwingComparator).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for tryMin(by:)")
|
||||
}
|
||||
|
||||
func testMaxOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(341).max().result, .success(341))
|
||||
XCTAssertEqual(Sut<Int>(nil).max().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").max().result, .failure("error"))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(2).max(by: comparator).result, .success(2))
|
||||
XCTAssertEqual(Sut<Int>(nil).max(by: comparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").max(by: comparator).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for max(by:)")
|
||||
}
|
||||
|
||||
func testTryMaxOperatorSpecialization() {
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(2).tryMax(by: comparator).result, .success(2))
|
||||
XCTAssertEqual(Sut<Int>(nil).tryMax(by: comparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(2).tryMax(by: throwingComparator).result, .success(2))
|
||||
XCTAssertEqual(Sut<Int>(nil).tryMax(by: throwingComparator).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").tryMax(by: throwingComparator).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for tryMax(by:)")
|
||||
}
|
||||
|
||||
func testContainsOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10).contains(12).result, .success(false))
|
||||
XCTAssertEqual(Sut<Int>(10).contains(10).result, .success(true))
|
||||
XCTAssertEqual(Sut<Int>(nil).contains(10).result, .success(false))
|
||||
XCTAssertEqual(Sut<Int>("error").contains(12).result, .failure("error"))
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 < 100 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(64).contains(where: predicate).result, .success(true))
|
||||
XCTAssertEqual(Sut<Int>(112).contains(where: predicate).result, .success(false))
|
||||
XCTAssertEqual(Sut<Int>(nil).contains(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").contains(where: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryContainsOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(0).tryContains(where: predicate).result.get(), false)
|
||||
XCTAssertEqual(try Sut<Int>(12).tryContains(where: predicate).result.get(), true)
|
||||
XCTAssertEqual(try Sut<Int>(1).tryContains(where: predicate).result.get(), true)
|
||||
XCTAssertEqual(try Sut<Int>(-1).tryContains(where: predicate).result.get(), false)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryContains(where: predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryContains(where: predicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>(1).tryContains(where: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryContains(where: throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryContains(where: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 5)
|
||||
}
|
||||
|
||||
func testRemoveDuplicatesOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1000).removeDuplicates().result, .success(1000))
|
||||
XCTAssertEqual(Sut<Int>(nil).removeDuplicates().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").removeDuplicates().result, .failure("error"))
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 == $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(44).removeDuplicates(by: comparator).result, .success(44))
|
||||
XCTAssertEqual(Sut<Int>(nil).removeDuplicates(by: comparator).result,
|
||||
.success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").removeDuplicates(by: comparator).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count,
|
||||
0,
|
||||
"comparator should not be called for removeDuplicates(by:)")
|
||||
}
|
||||
|
||||
func testTryRemoveDuplicatesOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(44).tryRemoveDuplicates(by: comparator).result.get(),
|
||||
44)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryRemoveDuplicates(by: comparator).result.get())
|
||||
XCTAssertEqual(
|
||||
try Sut<Int>(44).tryRemoveDuplicates(by: throwingComparator).result.get(),
|
||||
44
|
||||
)
|
||||
XCTAssertNil(
|
||||
try Sut<Int>(nil).tryRemoveDuplicates(by: throwingComparator).result.get()
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error")
|
||||
.tryRemoveDuplicates(by: throwingComparator).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count,
|
||||
0,
|
||||
"comparator should not be called for tryRemoveDuplicates(by:)")
|
||||
}
|
||||
|
||||
func testAllSatifyOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(0).allSatisfy(predicate).result, .success(false))
|
||||
XCTAssertEqual(Sut<Int>(1).allSatisfy(predicate).result, .success(true))
|
||||
XCTAssertEqual(Sut<Int>(nil).allSatisfy(predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").allSatisfy(predicate).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryAllSatifyOperatorSpecialization() {
|
||||
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 > 0 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(0).tryAllSatisfy(predicate).result.get(), false)
|
||||
XCTAssertEqual(try Sut<Int>(1).tryAllSatisfy(predicate).result.get(), true)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryAllSatisfy(predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>(1).tryAllSatisfy(throwingPredicate).result.get(),
|
||||
.oops)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryAllSatisfy(throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryAllSatisfy(throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testCollectOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(13).collect().result, .success([13]))
|
||||
XCTAssertEqual(Sut<Int>(nil).collect().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").collect().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testCountOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).count().result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).count().result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>("error").count().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testDropFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
|
||||
XCTAssertEqual(Sut<Int>(nil).dropFirst().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").dropFirst().result, .success(nil))
|
||||
}
|
||||
|
||||
func testDropWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).drop(while: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).drop(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").drop(while: predicate).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryDropWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryDrop(while: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryDrop(while: predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryDrop(while: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryDrop(while: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryDrop(while: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryDrop(while: throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryDrop(while: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(3).first().result, .success(3))
|
||||
XCTAssertEqual(Sut<Int>(nil).first().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").first().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).first(where: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).first(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).first(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").first(where: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryFirst(where: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryFirst(where: predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryFirst(where: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryFirst(where: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryFirst(where: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryFirst(where: throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryFirst(where: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testLastOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(4).last().result, .success(4))
|
||||
XCTAssertEqual(Sut<Int>(nil).last().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").last().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).last(where: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).last(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).last(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").last(where: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryLast(where: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryLast(where: predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryLast(where: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryLast(where: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryLast(where: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryLast(where: throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryLast(where: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testFilterOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).filter(predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).filter(predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).filter(predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").filter(predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryFilterOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryFilter(predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryFilter(predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryFilter(predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryFilter(predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryFilter(throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryFilter(throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryFilter(throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testIgnoreOutputOperatorSpecialization() {
|
||||
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
|
||||
XCTAssertTrue(Sut<Double>("error").ignoreOutput().completeImmediately)
|
||||
|
||||
do {
|
||||
var completion: Subscribers.Completion<TestingError>?
|
||||
_ = Sut<Double>("error").ignoreOutput()
|
||||
.sink(receiveCompletion: { completion = $0 },
|
||||
receiveValue: { _ in })
|
||||
|
||||
switch completion {
|
||||
case .finished?:
|
||||
break
|
||||
default:
|
||||
XCTFail("ignoreOutput should send 'finished' completion for Optional")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String = { count += 1; return String($0) }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).map(transform).result, .success("42"))
|
||||
XCTAssertEqual(Sut<Int>(nil).map(transform).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testTryMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String = { count += 1; return String($0) }
|
||||
let throwingTrasnform: (Int) throws -> String = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryMap(transform).result.get(), "42")
|
||||
XCTAssertNil(try Sut<Int>(nil).tryMap(transform).result.get())
|
||||
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), "oops")
|
||||
XCTAssertNil(try Sut<Int>(nil).tryMap(throwingTrasnform).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
|
||||
"error")
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testCompactMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String? = {
|
||||
count += 1
|
||||
return $0 == 42 ? String($0) : nil
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
|
||||
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).compactMap(transform).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryCompactMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String? = {
|
||||
count += 1
|
||||
return $0 == 42 ? String($0) : nil
|
||||
}
|
||||
let throwingTrasnform: (Int) throws -> String? = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
|
||||
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryCompactMap(transform).result.get())
|
||||
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), .oops)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryMap(throwingTrasnform).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
|
||||
"error")
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testMapErrorOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(42).mapError { _ in TestingError.oops }.result,
|
||||
.success(42))
|
||||
XCTAssertEqual(Sut<Int>(nil).mapError { _ in TestingError.oops }.result,
|
||||
.success(nil))
|
||||
XCTAssertEqual(
|
||||
Sut<Int>("error")
|
||||
.mapError { TestingError(description: $0.description.uppercased()) }
|
||||
.result,
|
||||
.failure("ERROR")
|
||||
)
|
||||
}
|
||||
|
||||
func testReplaceErrorOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).replaceError(with: 100).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).replaceError(with: 100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").replaceError(with: 100).result, .success(100))
|
||||
}
|
||||
|
||||
func testReplaceEmptyOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).replaceEmpty(with: 100).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).replaceEmpty(with: 100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").replaceEmpty(with: 100).result,
|
||||
.failure("error"))
|
||||
}
|
||||
|
||||
func testRetryOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).retry().result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).retry().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").retry().result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(Sut<Int>(1).retry(100).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(nil).retry(100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").retry(100).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testReduceOperatorSpecialization() {
|
||||
var count = 0
|
||||
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(4).reduce(2, plus).result, .success(6))
|
||||
XCTAssertEqual(Sut<Int>(nil).reduce(2, plus).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").reduce(2, plus).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testTryReduceOperatorSpecialization() {
|
||||
var count = 0
|
||||
let multiply: (Int, Int) -> Int = { count += 1; return $0 * $1 }
|
||||
let throwing: (Int, Int) throws -> Int = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(4).tryReduce(2, multiply).result.get(), 8)
|
||||
XCTAssertNil(try Sut<Int>(nil).tryReduce(2, multiply).result.get())
|
||||
assertThrowsError(try Sut<Int>(4).tryReduce(2, throwing).result.get(), "oops")
|
||||
XCTAssertNil(try Sut<Int>(nil).tryReduce(2, throwing).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryReduce(2, throwing).result.get(),
|
||||
"error")
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testScanOperatorSpecialization() {
|
||||
var count = 0
|
||||
let plus: (Int, Int) -> Int = { count += 1; return $0 + $1 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(4).scan(2, plus).result, .success(6))
|
||||
XCTAssertEqual(Sut<Int>(nil).scan(2, plus).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").scan(2, plus).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 1)
|
||||
}
|
||||
|
||||
func testTryScanOperatorSpecialization() {
|
||||
var count = 0
|
||||
let multiply: (Int, Int) -> Int = { count += 1; return $0 * $1 }
|
||||
let throwing: (Int, Int) throws -> Int = { _, _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(4).tryScan(2, multiply).result.get(), 8)
|
||||
XCTAssertEqual(try Sut<Int>(nil).tryScan(2, multiply).result.get(), nil)
|
||||
assertThrowsError(try Sut<Int>(4).tryScan(2, throwing).result.get(), "oops")
|
||||
XCTAssertNil(try Sut<Int>(nil).tryScan(2, throwing).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryScan(2, throwing).result.get(),
|
||||
"error")
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testOutputAtIndexOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(nil).output(at: 0).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").output(at: 42).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testOutputInRangeOperatorSpecialization() {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
// If this is fixed, this test will fail in compatibility mode so we will need
|
||||
// to change our implementation to the correct one.
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).output(in: 0 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").output(in: 0 ..< 10).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testPrefixOperatorSpecialization() {
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
// If this is fixed, this test will fail in compatibility mode so we will need
|
||||
// to change our implementation to the correct one.
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(0).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(0).result, .failure("error"))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(1).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testPrefixWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(nil).prefix(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(while: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryPrefixWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(98).tryPrefix(while: predicate).result.get(), 98)
|
||||
XCTAssertNil(try Sut<Int>(99).tryPrefix(while: predicate).result.get())
|
||||
XCTAssertNil(try Sut<Int>(nil).tryPrefix(while: predicate).result.get())
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(98).tryPrefix(while: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
XCTAssertNil( try Sut<Int>(nil).tryPrefix(while: throwingPredicate).result.get())
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryPrefix(while: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
}
|
||||
|
||||
func testSetFailureTypeOperatorSpecialization() {
|
||||
|
||||
XCTAssertEqual(
|
||||
Publishers.Optional<Int, Never>(73)
|
||||
.setFailureType(to: TestingError.self)
|
||||
.result,
|
||||
.success(73)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class PrintTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -63,7 +63,7 @@ final class PrintTests: XCTestCase {
|
||||
XCTAssertEqual(publisher.send(10), .unlimited)
|
||||
downstreamSubscription?.cancel()
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Print"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.finished),
|
||||
@@ -75,7 +75,7 @@ final class PrintTests: XCTestCase {
|
||||
.cancelled])
|
||||
|
||||
let expectedOutput = """
|
||||
receive subscription: (OpenCombineTests.CustomSubscription)
|
||||
receive subscription: (CustomSubscription)
|
||||
callback subscription
|
||||
request unlimited
|
||||
callback request demand
|
||||
@@ -150,7 +150,7 @@ final class PrintTests: XCTestCase {
|
||||
XCTAssertEqual(publisher.send(10), .unlimited)
|
||||
downstreamSubscription?.cancel()
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Print"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.finished),
|
||||
@@ -162,7 +162,7 @@ final class PrintTests: XCTestCase {
|
||||
.cancelled])
|
||||
|
||||
let expectedOutput = """
|
||||
👉: receive subscription: (OpenCombineTests.CustomSubscription)
|
||||
👉: receive subscription: (CustomSubscription)
|
||||
callback subscription
|
||||
👉: request unlimited
|
||||
callback request demand
|
||||
@@ -199,7 +199,7 @@ final class PrintTests: XCTestCase {
|
||||
func testSynchronization() {
|
||||
|
||||
let stream = StringStream()
|
||||
let publisher = CustomPublisher(subscription: nil)
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: nil)
|
||||
let printer = publisher.print(to: stream)
|
||||
|
||||
let counter = Atomic(0)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// ReplaceNilTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Joseph Spadafora on 7/4/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class ReplaceNilTests: XCTestCase {
|
||||
static let allTests = [
|
||||
("testReplacesNilElement", testReplacesNilElement),
|
||||
("testExistingElementIsPreserved", testExistingElementIsPreserved),
|
||||
("testMultipleReplacements", testMultipleReplacements)
|
||||
]
|
||||
|
||||
func testReplacesNilElement() {
|
||||
// Given
|
||||
let nilPublisher = PassthroughSubject<Int?, Never>()
|
||||
let output = Int.random(in: 1...100)
|
||||
let sut = nilPublisher.replaceNil(with: output)
|
||||
let subscriber = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
}
|
||||
)
|
||||
|
||||
// When
|
||||
sut.subscribe(subscriber)
|
||||
nilPublisher.send(nil)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.value(output)])
|
||||
}
|
||||
|
||||
func testExistingElementIsPreserved() {
|
||||
// Given
|
||||
let nilPublisher = PassthroughSubject<Int?, Never>()
|
||||
let output = Int.random(in: 1...100)
|
||||
let sut = nilPublisher.replaceNil(with: output + 1)
|
||||
let subscriber = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
}
|
||||
)
|
||||
|
||||
// When
|
||||
sut.subscribe(subscriber)
|
||||
nilPublisher.send(output)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.value(output)])
|
||||
}
|
||||
|
||||
func testMultipleReplacements() {
|
||||
// Given
|
||||
let passthrough = PassthroughSubject<Int?, Never>()
|
||||
let sut = passthrough.replaceNil(with: 42)
|
||||
let subscriber = TrackingSubscriberBase<Int, Never>(
|
||||
receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
}
|
||||
)
|
||||
|
||||
// When
|
||||
sut.subscribe(subscriber)
|
||||
passthrough.send(1)
|
||||
passthrough.send(2)
|
||||
passthrough.send(nil)
|
||||
passthrough.send(4)
|
||||
passthrough.send(5)
|
||||
passthrough.send(nil)
|
||||
passthrough.send(completion: .finished)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(subscriber.history, [.subscription("PassthroughSubject"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(42),
|
||||
.value(4),
|
||||
.value(5),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
}
|
||||
+76
-311
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// OnceTests.swift
|
||||
// ResultPublisherTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 17.06.2019.
|
||||
@@ -13,8 +13,8 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
final class OnceTests: XCTestCase {
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class ResultPublisherTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testOnceSuccessNoInitialDemand", testOnceSuccessNoInitialDemand),
|
||||
@@ -23,6 +23,7 @@ final class OnceTests: XCTestCase {
|
||||
("testOnceFailure", testOnceFailure),
|
||||
("testFailureCancelOnSubscription", testFailureCancelOnSubscription),
|
||||
("testLifecycle", testLifecycle),
|
||||
("testCustomMirror", testCustomMirror),
|
||||
("testMinOperatorSpecialization", testMinOperatorSpecialization),
|
||||
("testTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
|
||||
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
|
||||
@@ -38,26 +39,12 @@ final class OnceTests: XCTestCase {
|
||||
testTryAllSatifyOperatorSpecialization),
|
||||
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
|
||||
("testCountOperatorSpecialization", testCountOperatorSpecialization),
|
||||
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
|
||||
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
|
||||
("testTryDropWhileOperatorSpecialization",
|
||||
testTryDropWhileOperatorSpecialization),
|
||||
("testFirstOperatorSpecialization", testFirstOperatorSpecialization),
|
||||
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
|
||||
("testTryFirstWhereOperatorSpecializtion",
|
||||
testTryFirstWhereOperatorSpecializtion),
|
||||
("testLastOperatorSpecialization", testLastOperatorSpecialization),
|
||||
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
|
||||
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
|
||||
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
|
||||
("testTryFilterOperatorSpecialization", testTryFilterOperatorSpecialization),
|
||||
("testIgnoreOutputOperatorSpecialization",
|
||||
testIgnoreOutputOperatorSpecialization),
|
||||
("testMapOperatorSpecialization", testMapOperatorSpecialization),
|
||||
("testTryMapOperatorSpecialization", testTryMapOperatorSpecialization),
|
||||
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
|
||||
("testTryCompactMapOperatorSpecialization",
|
||||
testTryCompactMapOperatorSpecialization),
|
||||
("testMapErrorOperatorSpecialization", testMapErrorOperatorSpecialization),
|
||||
("testReplaceErrorOperatorSpecialization",
|
||||
testReplaceErrorOperatorSpecialization),
|
||||
@@ -68,53 +55,70 @@ final class OnceTests: XCTestCase {
|
||||
("testTryReduceOperatorSpecialization", testTryReduceOperatorSpecialization),
|
||||
("testScanOperatorSpecialization", testScanOperatorSpecialization),
|
||||
("testTryScanOperatorSpecialization", testTryScanOperatorSpecialization),
|
||||
("testOutputAtIndexOperatorSpecialization",
|
||||
testOutputAtIndexOperatorSpecialization),
|
||||
("testOutputInRangeOperatorSpecialization",
|
||||
testOutputInRangeOperatorSpecialization),
|
||||
("testPrefixOperatorSpecialization", testPrefixOperatorSpecialization),
|
||||
("testPrefixWhileOperatorSpecialization", testPrefixWhileOperatorSpecialization),
|
||||
("testTryPrefixWhileOperatorSpecialization",
|
||||
testTryPrefixWhileOperatorSpecialization),
|
||||
("testSetFailureTypeOperatorSpecialization",
|
||||
testSetFailureTypeOperatorSpecialization),
|
||||
]
|
||||
|
||||
private typealias Sut<Output> = Publishers.Once<Output, TestingError>
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
private typealias ResultPublisher<Output, Failure: Error> =
|
||||
Result<Output, Failure>.Publisher
|
||||
|
||||
private func makePublisher<Output, Failure: Error>(
|
||||
_ result: Result<Output, Failure>
|
||||
) -> ResultPublisher<Output, Failure> {
|
||||
return result.publisher
|
||||
}
|
||||
#else
|
||||
private typealias ResultPublisher<Output, Failure: Error> =
|
||||
Result<Output, Failure>.OCombine.Publisher
|
||||
|
||||
private func makePublisher<Output, Failure: Error>(
|
||||
_ result: Result<Output, Failure>
|
||||
) -> ResultPublisher<Output, Failure> {
|
||||
return result.ocombine.publisher
|
||||
}
|
||||
#endif
|
||||
private typealias Sut<Output> = ResultPublisher<Output, TestingError>
|
||||
|
||||
private func makePublisher<Output>(
|
||||
_ output: Output
|
||||
) -> ResultPublisher<Output, TestingError> {
|
||||
return makePublisher(.success(output))
|
||||
}
|
||||
|
||||
func testOnceSuccessNoInitialDemand() {
|
||||
let success = Sut(42)
|
||||
let success = makePublisher(42)
|
||||
let tracking = TrackingSubscriber()
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(tracking.history, [.subscription("Once")])
|
||||
|
||||
tracking.subscriptions.first?.request(.max(100))
|
||||
tracking.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Once"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testOnceSuccessWithInitialDemand() {
|
||||
let just = Sut(42)
|
||||
let just = makePublisher(42)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
just.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Once"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
|
||||
func testSuccessCancelOnSubscription() {
|
||||
let success = Sut(42)
|
||||
let success = makePublisher(42)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(1)); $0.cancel() }
|
||||
)
|
||||
success.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Once"),
|
||||
.value(42),
|
||||
.completion(.finished)])
|
||||
}
|
||||
@@ -124,7 +128,7 @@ final class OnceTests: XCTestCase {
|
||||
let tracking = TrackingSubscriber()
|
||||
failure.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Empty"),
|
||||
.completion(.failure("failure"))])
|
||||
}
|
||||
|
||||
@@ -135,14 +139,14 @@ final class OnceTests: XCTestCase {
|
||||
)
|
||||
failure.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(tracking.history, [.subscription("Empty"),
|
||||
.completion(.failure("failure"))])
|
||||
}
|
||||
|
||||
func testLifecycle() {
|
||||
var deinitCount = 0
|
||||
do {
|
||||
let once = Sut(42)
|
||||
let once = makePublisher(42)
|
||||
let tracking = TrackingSubscriber(onDeinit: { deinitCount += 1 })
|
||||
once.subscribe(tracking)
|
||||
tracking.subscriptions.first?.cancel()
|
||||
@@ -150,6 +154,24 @@ final class OnceTests: XCTestCase {
|
||||
XCTAssertEqual(deinitCount, 1)
|
||||
}
|
||||
|
||||
func testCustomMirror() throws {
|
||||
let publisher = makePublisher(42)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { downstreamSubscription = $0 }
|
||||
)
|
||||
publisher.subscribe(tracking)
|
||||
|
||||
var reflected = ""
|
||||
try dump(XCTUnwrap(downstreamSubscription), to: &reflected)
|
||||
|
||||
XCTAssertEqual(reflected, """
|
||||
▿ Once #0
|
||||
- 42
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
// MARK: - Operator specializations for Once
|
||||
|
||||
func testMinOperatorSpecialization() {
|
||||
@@ -171,12 +193,13 @@ final class OnceTests: XCTestCase {
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
XCTAssertEqual(Sut<Int>(1).tryMin(by: comparator).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>(1).tryMin(by: throwingComparator).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>("error").tryMin(by: throwingComparator).result,
|
||||
.failure("error"))
|
||||
XCTAssertEqual(try Sut<Int>(1).tryMin(by: comparator).result.get(), 1)
|
||||
assertThrowsError(try Sut<Int>(1).tryMin(by: throwingComparator).result.get(),
|
||||
.oops)
|
||||
assertThrowsError(try Sut<Int>(.oops).tryMin(by: throwingComparator).result.get(),
|
||||
.oops)
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for tryMin(by:)")
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testMaxOperatorSpecialization() {
|
||||
@@ -199,12 +222,13 @@ final class OnceTests: XCTestCase {
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(2).tryMax(by: comparator).result, .success(2))
|
||||
XCTAssertEqual(Sut<Int>(2).tryMax(by: throwingComparator).result, .success(2))
|
||||
XCTAssertEqual(Sut<Int>("error").tryMax(by: throwingComparator).result,
|
||||
.failure("error"))
|
||||
XCTAssertEqual(try Sut<Int>(2).tryMax(by: comparator).result.get(), 2)
|
||||
assertThrowsError(try Sut<Int>(2).tryMax(by: throwingComparator).result.get(),
|
||||
.oops)
|
||||
assertThrowsError(try Sut<Int>(.oops).tryMax(by: throwingComparator).result.get(),
|
||||
.oops)
|
||||
|
||||
XCTAssertEqual(count, 0, "comparator should not be called for tryMax(by:)")
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testContainsOperatorSpecialization() {
|
||||
@@ -261,16 +285,17 @@ final class OnceTests: XCTestCase {
|
||||
func testTryRemoveDuplicatesOperatorSpecialization() {
|
||||
var count = 0
|
||||
let comparator: (Int, Int) -> Bool = { count += 1; return $0 > $1 }
|
||||
let throwingComparator: (Int, Int) throws -> Bool = { _, _ in
|
||||
let throwingComparator: (Int, Int) throws -> Bool = {
|
||||
XCTAssertEqual($0, $1)
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(44).tryRemoveDuplicates(by: comparator).result.get(),
|
||||
44)
|
||||
XCTAssertEqual(
|
||||
assertThrowsError(
|
||||
try Sut<Int>(44).tryRemoveDuplicates(by: throwingComparator).result.get(),
|
||||
44
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error")
|
||||
@@ -278,9 +303,7 @@ final class OnceTests: XCTestCase {
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count,
|
||||
0,
|
||||
"comparator should not be called for tryRemoveDuplicates(by:)")
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testAllSatifyOperatorSpecialization() {
|
||||
@@ -325,170 +348,16 @@ final class OnceTests: XCTestCase {
|
||||
XCTAssertEqual(Sut<Int>("error").count().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testDropFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst().result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(100).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(10000).dropFirst(0).result, .success(10000))
|
||||
XCTAssertEqual(Sut<Int>("error").dropFirst().result, .success(nil))
|
||||
}
|
||||
|
||||
func testDropWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).drop(while: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).drop(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").drop(while: predicate).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryDropWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 != 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryDrop(while: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryDrop(while: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryDrop(while: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryDrop(while: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryDrop(while: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testFirstOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(3).first().result, .success(3))
|
||||
XCTAssertEqual(Sut<Int>("error").first().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).first(where: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).first(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").first(where: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryFirstWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryFirst(where: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryFirst(where: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryFirst(where: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryFirst(where: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryFirst(where: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testLastOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(4).last().result, .success(4))
|
||||
XCTAssertEqual(Sut<Int>("error").last().result, .failure("error"))
|
||||
}
|
||||
|
||||
func testLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).last(where: predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).last(where: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").last(where: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryLastWhereOperatorSpecializtion() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryLast(where: predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryLast(where: predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryLast(where: predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryLast(where: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryLast(where: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testFilterOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).filter(predicate).result, .success(42))
|
||||
XCTAssertEqual(Sut<Int>(-13).filter(predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").filter(predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryFilterOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0 == 42 }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryFilter(predicate).result.get(), 42)
|
||||
XCTAssertNil(try Sut<Int>(-13).tryFilter(predicate).result.get())
|
||||
assertThrowsError(try Sut<Int>("error").tryFilter(predicate).result.get(),
|
||||
"error")
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(42).tryFilter(throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryFilter(throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testIgnoreOutputOperatorSpecialization() {
|
||||
XCTAssertTrue(Sut<Double>(13.0).ignoreOutput().completeImmediately)
|
||||
XCTAssertTrue(Sut<Double>("error").ignoreOutput().completeImmediately)
|
||||
@@ -521,40 +390,6 @@ final class OnceTests: XCTestCase {
|
||||
"error")
|
||||
}
|
||||
|
||||
func testCompactMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String? = {
|
||||
count += 1
|
||||
return $0 == 42 ? String($0) : nil
|
||||
}
|
||||
|
||||
XCTAssertEqual(Sut<Int>(42).compactMap(transform).result, .success("42"))
|
||||
XCTAssertEqual(Sut<Int>(100).compactMap(transform).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").map(transform).result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryCompactMapOperatorSpecialization() {
|
||||
var count = 0
|
||||
let transform: (Int) -> String? = {
|
||||
count += 1
|
||||
return $0 == 42 ? String($0) : nil
|
||||
}
|
||||
let throwingTrasnform: (Int) throws -> String? = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(42).tryCompactMap(transform).result.get(), "42")
|
||||
XCTAssertNil(try Sut<Int>(100).tryCompactMap(transform).result.get())
|
||||
assertThrowsError(try Sut<Int>(42).tryMap(throwingTrasnform).result.get(), .oops)
|
||||
assertThrowsError(try Sut<Int>("error").tryMap(throwingTrasnform).result.get(),
|
||||
"error")
|
||||
|
||||
XCTAssertEqual(count, 3)
|
||||
}
|
||||
|
||||
func testMapErrorOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(42).mapError { _ in TestingError.oops }.result,
|
||||
.success(42))
|
||||
@@ -578,9 +413,6 @@ final class OnceTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRetryOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(1).retry().result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>("error").retry().result, .failure("error"))
|
||||
|
||||
XCTAssertEqual(Sut<Int>(1).retry(100).result, .success(1))
|
||||
XCTAssertEqual(Sut<Int>("error").retry(100).result, .failure("error"))
|
||||
}
|
||||
@@ -609,77 +441,10 @@ final class OnceTests: XCTestCase {
|
||||
"error")
|
||||
}
|
||||
|
||||
func testOutputAtIndexOperatorSpecialization() {
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(at: 42).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").output(at: 42).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testOutputInRangeOperatorSpecialization() {
|
||||
// TODO: Broken in Apple's Combine? (FB6169621)
|
||||
// Empty range should result in a nil
|
||||
// If this is fixed, this test will fail in compatibility mode so we will need
|
||||
// to change our implementation to the correct one.
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 10).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: (-10)...).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ... 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: -10 ..< -5).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ..< 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 0 ... 0).result, .success(12))
|
||||
XCTAssertEqual(Sut<Int>(12).output(in: 1 ..< 10).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").output(in: 0 ..< 10).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testPrefixOperatorSpecialization() {
|
||||
// TODO: Seems broken in Apple's Combine (FB6168300)
|
||||
// If this is fixed, this test will fail in compatibility mode so we will need
|
||||
// to change our implementation to the correct one.
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(0).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(1000).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(0).result, .failure("error"))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(1).result, .failure("error"))
|
||||
}
|
||||
|
||||
func testPrefixWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
|
||||
|
||||
XCTAssertEqual(Sut<Int>(98).prefix(while: predicate).result, .success(98))
|
||||
XCTAssertEqual(Sut<Int>(99).prefix(while: predicate).result, .success(nil))
|
||||
XCTAssertEqual(Sut<Int>("error").prefix(while: predicate).result,
|
||||
.failure("error"))
|
||||
|
||||
XCTAssertEqual(count, 2)
|
||||
}
|
||||
|
||||
func testTryPrefixWhileOperatorSpecialization() {
|
||||
var count = 0
|
||||
let predicate: (Int) -> Bool = { count += 1; return $0.isMultiple(of: 2) }
|
||||
let throwingPredicate: (Int) throws -> Bool = { _ in
|
||||
count += 1
|
||||
throw TestingError.oops
|
||||
}
|
||||
|
||||
XCTAssertEqual(try Sut<Int>(98).tryPrefix(while: predicate).result.get(), 98)
|
||||
XCTAssertNil(try Sut<Int>(99).tryPrefix(while: predicate).result.get())
|
||||
|
||||
assertThrowsError(
|
||||
try Sut<Int>(98).tryPrefix(while: throwingPredicate).result.get(),
|
||||
.oops
|
||||
)
|
||||
assertThrowsError(
|
||||
try Sut<Int>("error").tryPrefix(while: throwingPredicate).result.get(),
|
||||
"error"
|
||||
)
|
||||
}
|
||||
|
||||
func testSetFailureTypeOperatorSpecialization() {
|
||||
|
||||
XCTAssertEqual(
|
||||
try Publishers.Once<Int, Never>(73)
|
||||
try ResultPublisher<Int, Never>(73)
|
||||
.setFailureType(to: TestingError.self)
|
||||
.result
|
||||
.get(),
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class SequenceTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -28,16 +28,12 @@ final class SequenceTests: XCTestCase {
|
||||
("testCollectOperatorSpecialization", testCollectOperatorSpecialization),
|
||||
("testCompactMapOperatorSpecialization", testCompactMapOperatorSpecialization),
|
||||
("testMinOperatorSpecialization", testMinOperatorSpecialization),
|
||||
("tesTryMinOperatorSpecialization", testTryMinOperatorSpecialization),
|
||||
("testMaxOperatorSpecialization", testMaxOperatorSpecialization),
|
||||
("tesTryMaxOperatorSpecialization", testTryMaxOperatorSpecialization),
|
||||
("testContainsOperatorSpecialization", testContainsOperatorSpecialization),
|
||||
("testTryContainsOperatorSpecialization", testTryContainsOperatorSpecialization),
|
||||
("testDropWhileOperatorSpecialization", testDropWhileOperatorSpecialization),
|
||||
("testDropFirstOperatorSpecialization", testDropFirstOperatorSpecialization),
|
||||
("testFirstWhereOperatorSpecializtion", testFirstWhereOperatorSpecializtion),
|
||||
("testTryFirstWhereOperatorSpecializtion",
|
||||
testTryFirstWhereOperatorSpecializtion),
|
||||
("testFilterOperatorSpecialization", testFilterOperatorSpecialization),
|
||||
("testIgnoreOutputOperatorSpecialization",
|
||||
testIgnoreOutputOperatorSpecialization),
|
||||
@@ -60,7 +56,6 @@ final class SequenceTests: XCTestCase {
|
||||
testOutputInRangeOperatorSpecialization),
|
||||
("testLastOperatorSpecialization", testLastOperatorSpecialization),
|
||||
("testLastWhereOperatorSpecializtion", testLastWhereOperatorSpecializtion),
|
||||
("testTryLastWhereOperatorSpecializtion", testTryLastWhereOperatorSpecializtion),
|
||||
("testPrependVariadicOperatorSpezialization",
|
||||
testPrependVariadicOperatorSpezialization),
|
||||
("testPrependSequenceOperatorSpecialization",
|
||||
@@ -75,6 +70,14 @@ final class SequenceTests: XCTestCase {
|
||||
testAppendPublisherOperatorSpecialization),
|
||||
]
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST || !canImport(Combine)
|
||||
private typealias ResultPublisher<Output, Failure: Error> =
|
||||
Result<Output, Failure>.Publisher
|
||||
#else
|
||||
private typealias ResultPublisher<Output, Failure: Error> =
|
||||
Result<Output, Failure>.OCombine.Publisher
|
||||
#endif
|
||||
|
||||
func testEmptySequence() {
|
||||
|
||||
let emptyCounter = Counter(upperBound: 0)
|
||||
@@ -89,7 +92,7 @@ final class SequenceTests: XCTestCase {
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Empty"),
|
||||
.completion(.finished)])
|
||||
XCTAssertEqual(emptyCounter.state, 1)
|
||||
}
|
||||
@@ -115,18 +118,18 @@ final class SequenceTests: XCTestCase {
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(3))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3)])
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(5))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
@@ -138,7 +141,7 @@ final class SequenceTests: XCTestCase {
|
||||
|
||||
subscriber.subscriptions.first?.request(.none)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
@@ -150,7 +153,7 @@ final class SequenceTests: XCTestCase {
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
@@ -165,8 +168,9 @@ final class SequenceTests: XCTestCase {
|
||||
var reflected = ""
|
||||
try dump(XCTUnwrap(subscriber.subscriptions.first), to: &reflected)
|
||||
XCTAssertEqual(reflected, """
|
||||
▿ Sequence #0
|
||||
- sequence: 0 elements
|
||||
▿ Sequence
|
||||
▿ subscription: Sequence #0
|
||||
- sequence: 0 elements
|
||||
|
||||
""")
|
||||
}
|
||||
@@ -183,12 +187,12 @@ final class SequenceTests: XCTestCase {
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
|
||||
.value(1)])
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.value(3),
|
||||
@@ -207,12 +211,12 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
|
||||
.value(1)])
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(1))
|
||||
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
|
||||
.value(1)])
|
||||
}
|
||||
|
||||
@@ -231,10 +235,10 @@ final class SequenceTests: XCTestCase {
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
|
||||
publisher.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
|
||||
|
||||
subscriber.subscriptions.first?.request(.max(3))
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty),
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Sequence"),
|
||||
.value(1),
|
||||
.value(2),
|
||||
.completion(.finished)])
|
||||
@@ -253,7 +257,7 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
XCTAssertTrue(subscriber.history.isEmpty)
|
||||
publisher.subscribe(subscriber)
|
||||
XCTAssertEqual(subscriber.history, [.subscription(Subscriptions.empty)])
|
||||
XCTAssertEqual(subscriber.history, [.subscription("Counter")])
|
||||
XCTAssertNotNil(subscription)
|
||||
}
|
||||
|
||||
@@ -298,34 +302,12 @@ final class SequenceTests: XCTestCase {
|
||||
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).min(by: >), .init(5))
|
||||
}
|
||||
|
||||
func testTryMinOperatorSpecialization() {
|
||||
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMin(by: <).result.get(), -1)
|
||||
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMin(by: >).result.get(), 5)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>()).tryMin(by: throwing).result.get()
|
||||
)
|
||||
XCTAssertEqual(try makePublisher([10]).tryMin(by: throwing).result.get(), 10)
|
||||
assertThrowsError(try makePublisher([10, 20]).tryMin(by: throwing).result.get(),
|
||||
.oops)
|
||||
}
|
||||
|
||||
func testMaxOperatorSpecialization() {
|
||||
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).max(), .init(nil))
|
||||
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).max(), .init(5))
|
||||
XCTAssertEqual(makePublisher([3, 4, 5, -1, 2]).max(by: >), .init(-1))
|
||||
}
|
||||
|
||||
func testTryMaxOperatorSpecialization() {
|
||||
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMax(by: <).result.get(), 5)
|
||||
XCTAssertEqual(try makePublisher([3, 4, 5, -1, 2]).tryMax(by: >).result.get(), -1)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>()).tryMax(by: throwing).result.get()
|
||||
)
|
||||
XCTAssertEqual(try makePublisher([10]).tryMax(by: throwing).result.get(), 10)
|
||||
assertThrowsError(try makePublisher([10, 20]).tryMax(by: throwing).result.get(),
|
||||
.oops)
|
||||
}
|
||||
|
||||
func testContainsOperatorSpecialization() {
|
||||
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).contains(12), .init(false))
|
||||
XCTAssertEqual(makePublisher(0 ..< 12).contains(12), .init(false))
|
||||
@@ -374,28 +356,6 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testTryFirstWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(
|
||||
try makePublisher(1 ..< 9).tryFirst { $0.isMultiple(of: 4) }.result.get(),
|
||||
4
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(1 ..< 9).tryFirst { $0.isMultiple(of: 13) }.result.get()
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>())
|
||||
.tryFirst { $0.isMultiple(of: 13) }.result.get()
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>())
|
||||
.tryFirst(where: throwing).result.get()
|
||||
)
|
||||
assertThrowsError(
|
||||
try makePublisher(0 ..< 9).tryFirst(where: throwing).result.get(),
|
||||
.oops
|
||||
)
|
||||
}
|
||||
|
||||
func testFilterOperatorSpecialization() {
|
||||
XCTAssertEqual(makePublisher(0 ..< 10).filter { $0.isMultiple(of: 3) },
|
||||
.init(sequence: [0, 3, 6, 9]))
|
||||
@@ -483,10 +443,21 @@ final class SequenceTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCountOperatorSpecialization() {
|
||||
XCTAssertEqual(makePublisher(0 ..< .max).count(), Publishers.Once(.max))
|
||||
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).count(), Publishers.Once(0))
|
||||
XCTAssertEqual(makePublisher([1, 1, 1, 1, 1, 1]).count(), Publishers.Optional(6))
|
||||
XCTAssertEqual(makePublisher([]).count(), Publishers.Optional(0))
|
||||
XCTAssertEqual(makePublisher(0 ..< .max).count(), Just(.max))
|
||||
XCTAssertEqual(makePublisher(EmptyCollection<Int>()).count(), Just(0))
|
||||
XCTAssertEqual(makePublisher([1, 1, 1, 1, 1, 1]).count(), Just(6))
|
||||
XCTAssertEqual(
|
||||
makePublisher([1, 1, 1, 1, 1, 1])
|
||||
.setFailureType(to: TestingError.self)
|
||||
.count(),
|
||||
ResultPublisher(.success(6))
|
||||
)
|
||||
XCTAssertEqual(
|
||||
makePublisher([1, 2, 3, 4, 5, 6].lazy.filter { $0.isMultiple(of: 2) })
|
||||
.count(),
|
||||
ResultPublisher(.success(3))
|
||||
)
|
||||
XCTAssertEqual(makePublisher([]).count(), Just(0))
|
||||
}
|
||||
|
||||
func testOutputAtIndexOperatorSpecialization() {
|
||||
@@ -554,28 +525,6 @@ final class SequenceTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testTryLastWhereOperatorSpecializtion() {
|
||||
XCTAssertEqual(
|
||||
try makePublisher(1 ..< 9).tryLast { $0.isMultiple(of: 4) }.result.get(),
|
||||
8
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(1 ..< 9).tryLast { $0.isMultiple(of: 13) }.result.get()
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>())
|
||||
.tryLast { $0.isMultiple(of: 13) }.result.get()
|
||||
)
|
||||
XCTAssertNil(
|
||||
try makePublisher(EmptyCollection<Int>())
|
||||
.tryLast(where: throwing).result.get()
|
||||
)
|
||||
assertThrowsError(
|
||||
try makePublisher(0 ..< 9).tryLast(where: throwing).result.get(),
|
||||
.oops
|
||||
)
|
||||
}
|
||||
|
||||
func testPrependVariadicOperatorSpezialization() {
|
||||
let baseCollection = TrackingCollection<Int>([4, 5, 6, 7])
|
||||
|
||||
@@ -746,30 +695,30 @@ private final class Counter: Sequence, IteratorProtocol, CustomStringConvertible
|
||||
/// If both Foundation and OpenCombine are imported, Apple's Combine
|
||||
/// extensions leak through Foundation, which results in the following error:
|
||||
///
|
||||
/// let publisher = [1, 2, 3, 4].publisher()
|
||||
/// let publisher = [1, 2, 3, 4].publisher
|
||||
/// ^
|
||||
/// error: ambiguous use of 'publisher()'
|
||||
/// error: ambiguous use of 'publisher'
|
||||
///
|
||||
/// This could be fixed by explicitly specifying a type:
|
||||
///
|
||||
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher()
|
||||
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher
|
||||
///
|
||||
/// But this won't compile when testing compatibility, since compatibility tests
|
||||
/// don't import OpenCombine. This could be fixed as well like this:
|
||||
///
|
||||
/// #if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
/// let publisher: Combine.Publishers.Sequence = [1, 2, 3, 4].publisher()
|
||||
/// let publisher: Combine.Publishers.Sequence = [1, 2, 3, 4].publisher
|
||||
/// #else
|
||||
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher()
|
||||
/// let publisher: OpenCombine.Publishers.Sequence = [1, 2, 3, 4].publisher
|
||||
/// #endif
|
||||
///
|
||||
/// But this is too verbose. This function provides a more concise way:
|
||||
///
|
||||
/// let publisher = makePublisher([1, 2, 3, 4])
|
||||
///
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private func makePublisher<Elements: Sequence>(
|
||||
_ elements: Elements
|
||||
) -> Publishers.Sequence<Elements, Never> {
|
||||
return elements.publisher()
|
||||
return elements.publisher
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// SetFailureTypeTests.swift
|
||||
//
|
||||
//
|
||||
// Created by Sergej Jaskiewicz on 10.07.2019.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if OPENCOMBINE_COMPATIBILITY_TEST
|
||||
import Combine
|
||||
#else
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class SetFailureTypeTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testEmpty", testEmpty),
|
||||
("testForwardingValues", testForwardingValues),
|
||||
("testNoDemand", testNoDemand),
|
||||
("testDemandSubscribe", testDemandSubscribe),
|
||||
("testDemandSend", testDemandSend),
|
||||
("testCompletion", testCompletion),
|
||||
("testCancel", testCancel),
|
||||
("testCancelAlreadyCancelled", testCancelAlreadyCancelled),
|
||||
]
|
||||
|
||||
func testEmpty() {
|
||||
let tracking = TrackingSubscriberBase<Int, TestingError>(
|
||||
receiveSubscription: { $0.request(.unlimited) }
|
||||
)
|
||||
let publisher = TrackingSubjectBase<Int, Never>(
|
||||
receiveSubscriber: {
|
||||
XCTAssertEqual(String(describing: $0), "SetFailureType")
|
||||
}
|
||||
)
|
||||
|
||||
publisher
|
||||
.setFailureType(to: Never.self)
|
||||
.setFailureType(to: TestingError.self)
|
||||
.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(tracking.history, [.subscription("PassthroughSubject")])
|
||||
}
|
||||
|
||||
func testForwardingValues() {
|
||||
let publisher = PassthroughSubject<Int, Never>()
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
|
||||
publisher.send(1)
|
||||
sft.subscribe(tracking)
|
||||
publisher.send(2)
|
||||
publisher.send(3)
|
||||
publisher.send(completion: .finished)
|
||||
publisher.send(5)
|
||||
|
||||
XCTAssertEqual(tracking.history, [
|
||||
.subscription("PassthroughSubject"),
|
||||
.value(2),
|
||||
.value(3),
|
||||
.completion(.finished)
|
||||
])
|
||||
}
|
||||
|
||||
func testNoDemand() {
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
let tracking = TrackingSubscriber()
|
||||
|
||||
sft.subscribe(tracking)
|
||||
|
||||
XCTAssert(subscription.history.isEmpty)
|
||||
}
|
||||
|
||||
func testDemandSubscribe() {
|
||||
let expectedSubscribeDemand = 42
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.max(expectedSubscribeDemand)) }
|
||||
)
|
||||
|
||||
sft.subscribe(tracking)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.max(expectedSubscribeDemand))])
|
||||
}
|
||||
|
||||
func testDemandSend() {
|
||||
|
||||
var expectedReceiveValueDemand = 4
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
let tracking = TrackingSubscriber(
|
||||
receiveSubscription: { $0.request(.unlimited) },
|
||||
receiveValue: { _ in .max(expectedReceiveValueDemand) }
|
||||
)
|
||||
|
||||
sft.subscribe(tracking)
|
||||
XCTAssertEqual(publisher.send(0), .max(4))
|
||||
|
||||
expectedReceiveValueDemand = 120
|
||||
|
||||
XCTAssertEqual(publisher.send(0), .max(120))
|
||||
}
|
||||
|
||||
func testCompletion() {
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
let tracking = TrackingSubscriber(receiveSubscription: { $0.request(.unlimited) })
|
||||
|
||||
sft.subscribe(tracking)
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited)])
|
||||
XCTAssertEqual(
|
||||
tracking.history,
|
||||
[.subscription("CustomSubscription"), .completion(.finished)]
|
||||
)
|
||||
}
|
||||
|
||||
func testCancel() throws {
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
|
||||
var downstreamSubscription: Subscription?
|
||||
|
||||
let tracking = TrackingSubscriber(receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
})
|
||||
|
||||
sft.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
XCTAssertEqual(publisher.send(1), .none)
|
||||
publisher.send(completion: .finished)
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited), .cancelled])
|
||||
}
|
||||
|
||||
func testCancelAlreadyCancelled() throws {
|
||||
|
||||
let subscription = CustomSubscription()
|
||||
let publisher = CustomPublisherBase<Int, Never>(subscription: subscription)
|
||||
let sft = publisher.setFailureType(to: TestingError.self)
|
||||
var downstreamSubscription: Subscription?
|
||||
let tracking = TrackingSubscriber(receiveSubscription: {
|
||||
$0.request(.unlimited)
|
||||
downstreamSubscription = $0
|
||||
})
|
||||
|
||||
sft.subscribe(tracking)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
downstreamSubscription?.request(.unlimited)
|
||||
try XCTUnwrap(downstreamSubscription).cancel()
|
||||
|
||||
XCTAssertEqual(subscription.history, [.requested(.unlimited),
|
||||
.cancelled,
|
||||
.requested(.unlimited),
|
||||
.cancelled])
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class AssignTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -70,19 +70,19 @@ final class AssignTests: XCTestCase {
|
||||
let subscription1 = CustomSubscription()
|
||||
assign.receive(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.lastRequested, .unlimited)
|
||||
XCTAssertFalse(subscription1.canceled)
|
||||
XCTAssertFalse(subscription1.cancelled)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
assign.receive(subscription: subscription2)
|
||||
XCTAssertFalse(subscription1.canceled)
|
||||
XCTAssertTrue(subscription2.canceled)
|
||||
XCTAssertFalse(subscription1.cancelled)
|
||||
XCTAssertTrue(subscription2.cancelled)
|
||||
|
||||
assign.receive(subscription: subscription1)
|
||||
XCTAssertTrue(subscription1.canceled)
|
||||
XCTAssertTrue(subscription1.cancelled)
|
||||
|
||||
subscription1.canceled = false
|
||||
subscription1.cancelled = false
|
||||
assign.receive(completion: .finished)
|
||||
XCTAssertTrue(subscription1.canceled)
|
||||
XCTAssertTrue(subscription1.cancelled)
|
||||
}
|
||||
|
||||
func testReceiveValue() {
|
||||
|
||||
@@ -15,7 +15,7 @@ import OpenCombine
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class CompletionTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
|
||||
@@ -13,7 +13,7 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class SinkTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
@@ -27,37 +27,37 @@ final class SinkTests: XCTestCase {
|
||||
private typealias Sut = Subscribers.Sink<Int, Never>
|
||||
|
||||
func testDescription() {
|
||||
let sink = Sut(receiveValue: { _ in })
|
||||
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
|
||||
|
||||
XCTAssertEqual(sink.description, "Sink")
|
||||
XCTAssertEqual(sink.playgroundDescription as? String, "Sink")
|
||||
}
|
||||
|
||||
func testReflection() {
|
||||
let sink = Sut(receiveValue: { _ in })
|
||||
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
|
||||
XCTAssert(sink.customMirror.children.isEmpty)
|
||||
}
|
||||
|
||||
func testSubscription() {
|
||||
|
||||
let sink = Sut(receiveValue: { _ in })
|
||||
let sink = Sut(receiveCompletion: { _ in }, receiveValue: { _ in })
|
||||
|
||||
let subscription1 = CustomSubscription()
|
||||
sink.receive(subscription: subscription1)
|
||||
XCTAssertEqual(subscription1.lastRequested, .unlimited)
|
||||
XCTAssertFalse(subscription1.canceled)
|
||||
XCTAssertFalse(subscription1.cancelled)
|
||||
|
||||
let subscription2 = CustomSubscription()
|
||||
sink.receive(subscription: subscription2)
|
||||
XCTAssertFalse(subscription1.canceled)
|
||||
XCTAssertTrue(subscription2.canceled)
|
||||
XCTAssertFalse(subscription1.cancelled)
|
||||
XCTAssertTrue(subscription2.cancelled)
|
||||
|
||||
sink.receive(subscription: subscription1)
|
||||
XCTAssertTrue(subscription1.canceled)
|
||||
XCTAssertTrue(subscription1.cancelled)
|
||||
|
||||
subscription1.canceled = false
|
||||
subscription1.cancelled = false
|
||||
sink.receive(completion: .finished)
|
||||
XCTAssertFalse(subscription1.canceled)
|
||||
XCTAssertFalse(subscription1.cancelled)
|
||||
}
|
||||
|
||||
func testReceiveValue() {
|
||||
@@ -114,6 +114,6 @@ final class SinkTests: XCTestCase {
|
||||
}
|
||||
|
||||
publisher.send(100)
|
||||
XCTAssertEqual(value, 100)
|
||||
XCTAssertEqual(value, 42)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,27 @@ import Combine
|
||||
import OpenCombine
|
||||
#endif
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
final class SubscribersDemandTests: XCTestCase {
|
||||
|
||||
static let allTests = [
|
||||
("testCrashesOnNegativeValue", testCrashesOnNegativeValue),
|
||||
("testAddition", testAddition),
|
||||
("testSubtraction", testSubtraction),
|
||||
("testMultiplication", testMultiplication),
|
||||
("testComparison", testComparison),
|
||||
("testMax", testMax),
|
||||
("testDescription", testDescription),
|
||||
("testEncodeDecode", testEncodeDecode),
|
||||
("testEncodeDecodeJSON", testEncodeDecodeJSON),
|
||||
("testEncodeDecodePlist", testEncodeDecodePlist),
|
||||
]
|
||||
|
||||
func testCrashesOnNegativeValue() {
|
||||
assertCrashes {
|
||||
_ = Subscribers.Demand.max(-1)
|
||||
}
|
||||
}
|
||||
|
||||
func testAddition() {
|
||||
|
||||
XCTAssertEqual(.max(42) + .unlimited, Subscribers.Demand.unlimited)
|
||||
@@ -203,35 +211,129 @@ final class SubscribersDemandTests: XCTestCase {
|
||||
XCTAssertEqual(Subscribers.Demand.unlimited.description, "unlimited")
|
||||
}
|
||||
|
||||
func testEncodeDecode() throws {
|
||||
func testEncodeDecodeJSON() throws {
|
||||
try testEncodeDecode(
|
||||
encoder: JSONEncoder(),
|
||||
decoder: JSONDecoder(),
|
||||
expectedEncodedMax: #"{"value":42}"#,
|
||||
expectedEncodedUnlimited: #"{"value":\#(UInt(Int.max) + 1)}"#,
|
||||
illFormedNegative: #"{"value":-1}"#,
|
||||
illFormedTooBig: #"{"value":\#(UInt(Int.max) + 2)}"#,
|
||||
stringToDecoderInput: { Data($0.utf8) },
|
||||
encoderOutputToString: { String(decoding: $0, as: UTF8.self) }
|
||||
)
|
||||
}
|
||||
|
||||
let jsonMax = #"{"rawValue":42}"#
|
||||
let jsonUnlimited = #"{"rawValue":\#(UInt(Int.max) + 1)}"#
|
||||
let jsonIllFormedNegative = #"{"rawValue":-1}"#
|
||||
let jsonIllFormedTooBig = #"{"rawValue":\#(UInt(Int.max) + 2)}"#
|
||||
func testEncodeDecodePlist() throws {
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .xml
|
||||
try testEncodeDecode(
|
||||
encoder: encoder,
|
||||
decoder: PropertyListDecoder(),
|
||||
expectedEncodedMax: """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
\t<key>value</key>
|
||||
\t<integer>42</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
let encodedMax = try JSONEncoder().encode(Subscribers.Demand.max(42))
|
||||
XCTAssertEqual(String(decoding: encodedMax, as: UTF8.self), jsonMax)
|
||||
""",
|
||||
expectedEncodedUnlimited: """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
\t<key>value</key>
|
||||
\t<integer>\(UInt(Int.max) + 1)</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
let encodedUnlimited = try JSONEncoder().encode(Subscribers.Demand.unlimited)
|
||||
XCTAssertEqual(String(decoding: encodedUnlimited, as: UTF8.self), jsonUnlimited)
|
||||
""",
|
||||
illFormedNegative: """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
\t<key>value</key>
|
||||
\t<integer>-1</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
let decodedMax = try JSONDecoder().decode(Subscribers.Demand.self,
|
||||
from: Data(jsonMax.utf8))
|
||||
""",
|
||||
illFormedTooBig: """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" \
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
\t<key>value</key>
|
||||
\t<integer>\(UInt(Int.max) + 2)</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
""",
|
||||
stringToDecoderInput: { Data($0.utf8) },
|
||||
encoderOutputToString: { String(decoding: $0, as: UTF8.self) }
|
||||
)
|
||||
}
|
||||
|
||||
private func testEncodeDecode<Encoder: TopLevelEncoder, Decoder: TopLevelDecoder>(
|
||||
encoder: Encoder,
|
||||
decoder: Decoder,
|
||||
expectedEncodedMax: String,
|
||||
expectedEncodedUnlimited: String,
|
||||
illFormedNegative: String,
|
||||
illFormedTooBig: String,
|
||||
stringToDecoderInput: (String) -> Decoder.Input,
|
||||
encoderOutputToString: (Encoder.Output) -> String
|
||||
) throws where Decoder.Input == Encoder.Output {
|
||||
|
||||
let encodedMax = try encoderOutputToString(
|
||||
encoder.encode(DemandKeyedWrapper.max(42))
|
||||
)
|
||||
XCTAssertEqual(encodedMax, expectedEncodedMax)
|
||||
|
||||
let encodedUnlimited = try encoderOutputToString(
|
||||
encoder.encode(DemandKeyedWrapper.unlimited)
|
||||
)
|
||||
XCTAssertEqual(encodedUnlimited, expectedEncodedUnlimited)
|
||||
|
||||
let decodedMax = try decoder
|
||||
.decode(DemandKeyedWrapper.self,
|
||||
from: stringToDecoderInput(expectedEncodedMax))
|
||||
XCTAssertEqual(decodedMax, .max(42))
|
||||
|
||||
let decodedUnlimited = try JSONDecoder().decode(Subscribers.Demand.self,
|
||||
from: Data(jsonUnlimited.utf8))
|
||||
let decodedUnlimited = try decoder
|
||||
.decode(DemandKeyedWrapper.self,
|
||||
from: stringToDecoderInput(expectedEncodedUnlimited))
|
||||
XCTAssertEqual(decodedUnlimited, .unlimited)
|
||||
|
||||
XCTAssertThrowsError(
|
||||
try JSONDecoder().decode(Subscribers.Demand.self,
|
||||
from: Data(jsonIllFormedNegative.utf8))
|
||||
try decoder.decode(DemandKeyedWrapper.self,
|
||||
from: stringToDecoderInput(illFormedNegative))
|
||||
)
|
||||
|
||||
let decodedIllFormedTooBig = try JSONDecoder()
|
||||
.decode(Subscribers.Demand.self, from: Data(jsonIllFormedTooBig.utf8))
|
||||
let decodedIllFormedTooBig = try decoder
|
||||
.decode(DemandKeyedWrapper.self,
|
||||
from: stringToDecoderInput(illFormedTooBig))
|
||||
|
||||
XCTAssertEqual(decodedIllFormedTooBig.description, "max(\(UInt(Int.max) + 2))")
|
||||
XCTAssertEqual(decodedIllFormedTooBig.value.description, "unlimited")
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, *)
|
||||
private struct DemandKeyedWrapper: Codable, Equatable {
|
||||
let value: Subscribers.Demand
|
||||
|
||||
static let unlimited = DemandKeyedWrapper(value: .unlimited)
|
||||
|
||||
static func max(_ value: Int) -> DemandKeyedWrapper {
|
||||
return DemandKeyedWrapper(value: .max(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,32 @@ import XCTest
|
||||
#if !canImport(ObjectiveC)
|
||||
public func allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(SubscribersDemandTests.allTests),
|
||||
testCase(CompletionTests.allTests),
|
||||
testCase(PassthroughSubjectTests.allTests),
|
||||
testCase(CurrentValueSubjectTests.allTests),
|
||||
testCase(ImmediateSchedulerTests.allTests),
|
||||
testCase(AnySubscriberTests.allTests),
|
||||
testCase(AnyPublisherTests.allTests),
|
||||
testCase(AnySubjectTests.allTests),
|
||||
testCase(CombineIdentifierTests.allTests),
|
||||
testCase(AnyCancellableTests.allTests),
|
||||
testCase(MulticastTests.allTests),
|
||||
testCase(AnyPublisherTests.allTests),
|
||||
testCase(AssignTests.allTests),
|
||||
testCase(SinkTests.allTests),
|
||||
testCase(DropWhileTests.allTests),
|
||||
testCase(CombineIdentifierTests.allTests),
|
||||
testCase(CompletionTests.allTests),
|
||||
testCase(CurrentValueSubjectTests.allTests),
|
||||
testCase(DecodeTests.allTests),
|
||||
testCase(EncodeTests.allTests),
|
||||
testCase(MapTests.allTests),
|
||||
testCase(DropWhileTests.allTests),
|
||||
testCase(EmptyTests.allTests),
|
||||
testCase(JustTests.allTests),
|
||||
testCase(OnceTests.allTests),
|
||||
testCase(OptionalTests.allTests),
|
||||
testCase(EncodeTests.allTests),
|
||||
testCase(FailTests.allTests),
|
||||
testCase(ImmediateSchedulerTests.allTests),
|
||||
testCase(JustTests.allTests),
|
||||
testCase(MapErrorTests.allTests),
|
||||
testCase(MapTests.allTests),
|
||||
testCase(MulticastTests.allTests),
|
||||
testCase(ResultPublisherTests.allTests),
|
||||
testCase(OptionalPublisherTests.allTests),
|
||||
testCase(PassthroughSubjectTests.allTests),
|
||||
testCase(PrintTests.allTests),
|
||||
testCase(PublisherTests.allTests),
|
||||
testCase(ReplaceNilTests.allTests),
|
||||
testCase(SetFailureTypeTests.allTests),
|
||||
testCase(SequenceTests.allTests),
|
||||
testCase(SinkTests.allTests),
|
||||
testCase(SubscribersDemandTests.allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -DOPENCOMBINE_COMPATIBILITY_TEST
|
||||
Reference in New Issue
Block a user