diff --git a/CombineExt.xcodeproj/project.pbxproj b/CombineExt.xcodeproj/project.pbxproj index fe9cb70..a96bf8a 100644 --- a/CombineExt.xcodeproj/project.pbxproj +++ b/CombineExt.xcodeproj/project.pbxproj @@ -37,6 +37,8 @@ C387777F24E6BF8F00FAD2D8 /* NwiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C387777D24E6BF6C00FAD2D8 /* NwiseTests.swift */; }; D836234824EA9446002353AC /* MergeMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = D836234724EA9446002353AC /* MergeMany.swift */; }; D836234A24EA9888002353AC /* MergeManyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D836234924EA9888002353AC /* MergeManyTests.swift */; }; + ECF0134D26B86DC000F1D286 /* AllSatisfy.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF0134C26B86DC000F1D286 /* AllSatisfy.swift */; }; + ECF0135426B9B79900F1D286 /* AllSatisfyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF0135226B9B50000F1D286 /* AllSatisfyTests.swift */; }; OBJ_100 /* ZipMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* ZipMany.swift */; }; OBJ_101 /* CurrentValueRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* CurrentValueRelay.swift */; }; OBJ_102 /* PassthroughRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* PassthroughRelay.swift */; }; @@ -122,6 +124,8 @@ "CombineExt::CombineExtTests::Product" /* CombineExtTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = CombineExtTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D836234724EA9446002353AC /* MergeMany.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeMany.swift; sourceTree = ""; }; D836234924EA9888002353AC /* MergeManyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergeManyTests.swift; sourceTree = ""; }; + ECF0134C26B86DC000F1D286 /* AllSatisfy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllSatisfy.swift; sourceTree = ""; }; + ECF0135226B9B50000F1D286 /* AllSatisfyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllSatisfyTests.swift; sourceTree = ""; }; OBJ_10 /* Sink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sink.swift; sourceTree = ""; }; OBJ_12 /* Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Optional.swift; sourceTree = ""; }; OBJ_14 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; @@ -238,6 +242,7 @@ isa = PBXGroup; children = ( BF330EF724F2001F001281FC /* Internal */, + ECF0134C26B86DC000F1D286 /* AllSatisfy.swift */, OBJ_17 /* Amb.swift */, OBJ_18 /* AssignOwnership.swift */, OBJ_19 /* AssignToMany.swift */, @@ -286,6 +291,7 @@ OBJ_40 /* Tests */ = { isa = PBXGroup; children = ( + ECF0135226B9B50000F1D286 /* AllSatisfyTests.swift */, OBJ_41 /* AmbTests.swift */, OBJ_42 /* AssignOwnershipTests.swift */, OBJ_43 /* AssignToManyTests.swift */, @@ -557,6 +563,7 @@ OBJ_127 /* CurrentValueRelayTests.swift in Sources */, C387777F24E6BF8F00FAD2D8 /* NwiseTests.swift in Sources */, OBJ_128 /* DematerializeTests.swift in Sources */, + ECF0135426B9B79900F1D286 /* AllSatisfyTests.swift in Sources */, OBJ_129 /* FlatMapLatestTests.swift in Sources */, OBJ_130 /* MapManyTests.swift in Sources */, OBJ_131 /* MaterializeTests.swift in Sources */, @@ -612,6 +619,7 @@ BF330EF924F20032001281FC /* Timer.swift in Sources */, OBJ_101 /* CurrentValueRelay.swift in Sources */, OBJ_102 /* PassthroughRelay.swift in Sources */, + ECF0134D26B86DC000F1D286 /* AllSatisfy.swift in Sources */, OBJ_103 /* Relay.swift in Sources */, OBJ_104 /* ReplaySubject.swift in Sources */, ); diff --git a/README.md b/README.md index 3f6b169..efaf80c 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ All operators, utilities and helpers respect Combine's publisher contract, inclu * [ignoreFailure](#ignoreFailure) * [mapToResult](#mapToResult) * [flatMapBatches(of:)](#flatMapBatchesof) +* [allSatisfy](#allSatisfy) ### Publishers * [AnyPublisher.create](#AnypublisherCreate) @@ -755,6 +756,38 @@ subscription = ints .finished ``` +------ + +### allSatisfy + +Returns a Boolean value indicating whether every element of a publisher `Collection` satisfies a given predicate. + +```swift +let intArrayPublisher = PassthroughSubject<[Int], Never>() + +intArrayPublisher + .allSatisfy { $0.isMultiple(of: 2) } + .sink { print("All multiples of 2? \($0)") } + +intArrayPublisher.send([2, 4, 6, 8, 10]) +intArrayPublisher.send([2, 4, 6, 9, 10]) + +let names = [Just("John"), Just("Jane"), Just("Jim"), Just("Jill"), Just("Joan")] + +names + .combineLatest() + .allSatisfy { $0.count <= 4 } + .sink { print("All short names? \($0)") } +``` + +#### Output + +```none +All multiples of 2? true +All multiples of 2? false +All short names? true +``` + ## Publishers This section outlines some of the custom Combine publishers CombineExt provides diff --git a/Sources/Operators/AllSatisfy.swift b/Sources/Operators/AllSatisfy.swift new file mode 100644 index 0000000..d7cc94a --- /dev/null +++ b/Sources/Operators/AllSatisfy.swift @@ -0,0 +1,54 @@ +// +// AllSatisfy.swift +// CombineExt +// +// Created by Vitaly Sender on 2/8/21. +// Copyright © 2021 Combine Community. All rights reserved. +// + +#if canImport(Combine) +import Combine + +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +public extension Publisher where Output: Collection { + /// Returns a Boolean value indicating whether every element of a publisher `Collection` satisfies a given predicate. + /// + /// Example usages: + /// + /// ``` + /// let intArrayPublisher = PassthroughSubject<[Int], Never>() + /// + /// intArrayPublisher + /// .allSatisfy { $0.isMultiple(of: 2) } + /// .sink { print("All multiples of 2? \($0)") } + /// + /// intArrayPublisher.send([2, 4, 6, 8, 10]) + /// intArrayPublisher.send([2, 4, 6, 9, 10]) + /// + /// // Output + /// All multiples of 2? true + /// All multiples of 2? false + /// ``` + /// + /// ``` + /// let names = [Just("John"), Just("Jane"), Just("Jim"), Just("Jill"), Just("Joan")] + /// + /// names + /// .combineLatest() + /// .allSatisfy { $0.count <= 4 } + /// .sink { print("All short names? \($0)") } + /// + /// // Output + /// All short names? true + /// ``` + /// + /// - parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element satisfies a condition. + /// + /// - returns: A publisher that represents whether all elements in the original publisher `Collection` satisfy `predicate`. + /// + func allSatisfy(_ predicate: @escaping (Output.Element) -> Bool) -> AnyPublisher { + map { $0.allSatisfy { predicate($0) } } + .eraseToAnyPublisher() + } +} +#endif diff --git a/Tests/AllSatisfyTests.swift b/Tests/AllSatisfyTests.swift new file mode 100644 index 0000000..5321add --- /dev/null +++ b/Tests/AllSatisfyTests.swift @@ -0,0 +1,66 @@ +// +// AllSatisfyTests.swift +// CombineExt +// +// Created by Vitaly Sender on 3/8/21. +// + +#if !os(watchOS) +import XCTest +import Combine + +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +final class AllSatisfyTests: XCTestCase { + var subscription: AnyCancellable! + + func testAllSatisfyWithEmptyArray() { + let source = PassthroughSubject<[Int], Never>() + var result = false + + subscription = source + .allSatisfy { $0.isMultiple(of: 2) } + .sink { result = $0 } + + XCTAssertFalse(result) + } + + func testAllSatisfyWithSingleElement() { + let source = PassthroughSubject<[Int], Never>() + var result = false + + subscription = source + .allSatisfy { $0.isMultiple(of: 2) } + .sink { result = $0 } + + source.send([2]) + + XCTAssertTrue(result) + } + + func testAllSatisfyWithMultipleElementsFailing() { + let source = PassthroughSubject<[Int], Never>() + var result = false + + subscription = source + .allSatisfy { $0.isMultiple(of: 2) } + .sink { result = $0 } + + source.send([1, 4]) + + XCTAssertFalse(result) + } + + func testAllSatisfyWithMultipleElementsSucceeding() { + let source = PassthroughSubject<[Int], Never>() + var result = false + + subscription = source + .allSatisfy { $0.isMultiple(of: 2) } + .sink { result = $0 } + + source.send([2, 4, 10]) + + XCTAssertTrue(result) + } +} +#endif