Skip to content

Commit

Permalink
Merge pull request #95 from rbeeger/allPassMatcher
Browse files Browse the repository at this point in the history
All pass matcher
  • Loading branch information
modocache committed Mar 13, 2015
2 parents f798741 + 429edd2 commit 6a679cb
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 0 deletions.
18 changes: 18 additions & 0 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,18 @@
1FD8CD771968AB07008ED995 /* ObjCMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FD8CD2D1968AB07008ED995 /* ObjCMatcher.swift */; };
DA9E8C821A414BB9002633C2 /* DSL+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9E8C811A414BB9002633C2 /* DSL+Wait.swift */; };
DA9E8C831A414BB9002633C2 /* DSL+Wait.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9E8C811A414BB9002633C2 /* DSL+Wait.swift */; };
DD72EC641A93874A002F7651 /* AllPassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD72EC631A93874A002F7651 /* AllPassTest.swift */; };
DD72EC651A93874A002F7651 /* AllPassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD72EC631A93874A002F7651 /* AllPassTest.swift */; };
DD9A9A8F19CF439B00706F49 /* BeIdenticalToObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9A9A8D19CF413800706F49 /* BeIdenticalToObjectTest.swift */; };
DD9A9A9019CF43AD00706F49 /* BeIdenticalToObjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9A9A8D19CF413800706F49 /* BeIdenticalToObjectTest.swift */; };
DDB1BC791A92235600F743C3 /* AllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1BC781A92235600F743C3 /* AllPass.swift */; };
DDB1BC7A1A92235600F743C3 /* AllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB1BC781A92235600F743C3 /* AllPass.swift */; };
DDB4D5ED19FE43C200E9D9FE /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB4D5EC19FE43C200E9D9FE /* Match.swift */; };
DDB4D5EE19FE43C200E9D9FE /* Match.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB4D5EC19FE43C200E9D9FE /* Match.swift */; };
DDB4D5F019FE442800E9D9FE /* MatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */; };
DDB4D5F119FE442800E9D9FE /* MatchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */; };
DDEFAEB41A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */; };
DDEFAEB51A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -330,9 +336,12 @@
1FFD729B1963FCAB00CD29A2 /* NimbleTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NimbleTests-Bridging-Header.h"; sourceTree = "<group>"; };
1FFD729C1963FCAB00CD29A2 /* Nimble-OSXTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Nimble-OSXTests-Bridging-Header.h"; sourceTree = "<group>"; };
DA9E8C811A414BB9002633C2 /* DSL+Wait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DSL+Wait.swift"; sourceTree = "<group>"; };
DD72EC631A93874A002F7651 /* AllPassTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllPassTest.swift; sourceTree = "<group>"; };
DD9A9A8D19CF413800706F49 /* BeIdenticalToObjectTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BeIdenticalToObjectTest.swift; sourceTree = "<group>"; };
DDB1BC781A92235600F743C3 /* AllPass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllPass.swift; sourceTree = "<group>"; };
DDB4D5EC19FE43C200E9D9FE /* Match.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Match.swift; sourceTree = "<group>"; };
DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatchTest.swift; sourceTree = "<group>"; };
DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCAllPassTest.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -468,6 +477,7 @@
1F299EAA19627B2D002641AF /* BeEmptyTest.swift */,
1F1B5AD31963E13900CA8BF9 /* BeAKindOfTest.swift */,
DDB4D5EF19FE442800E9D9FE /* MatchTest.swift */,
DD72EC631A93874A002F7651 /* AllPassTest.swift */,
);
path = Matchers;
sourceTree = "<group>";
Expand Down Expand Up @@ -503,6 +513,7 @@
DDB4D5EC19FE43C200E9D9FE /* Match.swift */,
1FD8CD1D1968AB07008ED995 /* MatcherProtocols.swift */,
1FD8CD1E1968AB07008ED995 /* RaisesException.swift */,
DDB1BC781A92235600F743C3 /* AllPass.swift */,
);
path = Matchers;
sourceTree = "<group>";
Expand Down Expand Up @@ -569,6 +580,7 @@
1F4A56991A3B3539009E1637 /* ObjCEqualTest.m */,
1F4A569C1A3B3565009E1637 /* ObjCMatchTest.m */,
1F4A569F1A3B359E009E1637 /* ObjCRaiseExceptionTest.m */,
DDEFAEB31A93CBE6005CA37A /* ObjCAllPassTest.m */,
);
path = objc;
sourceTree = "<group>";
Expand Down Expand Up @@ -771,6 +783,7 @@
1FD8CD501968AB07008ED995 /* BeLogical.swift in Sources */,
1FD8CD661968AB07008ED995 /* NMBExceptionCapture.m in Sources */,
DA9E8C821A414BB9002633C2 /* DSL+Wait.swift in Sources */,
DDB1BC791A92235600F743C3 /* AllPass.swift in Sources */,
1FD8CD3E1968AB07008ED995 /* BeAKindOf.swift in Sources */,
DDB4D5ED19FE43C200E9D9FE /* Match.swift in Sources */,
1FD8CD2E1968AB07008ED995 /* AssertionRecorder.swift in Sources */,
Expand Down Expand Up @@ -834,6 +847,7 @@
1F925EF6195C147800ED456B /* BeCloseToTest.swift in Sources */,
1F4A56791A3B32E3009E1637 /* ObjCBeGreaterThanOrEqualToTest.m in Sources */,
1F4A568B1A3B3407009E1637 /* ObjCBeTrueTest.m in Sources */,
DDEFAEB41A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */,
1F4A567F1A3B333F009E1637 /* ObjCBeLessThanTest.m in Sources */,
1F925EE6195C121200ED456B /* AsynchronousTest.swift in Sources */,
1F0648CC19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */,
Expand All @@ -843,6 +857,7 @@
1F925F08195C18CF00ED456B /* BeGreaterThanTest.swift in Sources */,
1F925F05195C18B700ED456B /* EqualTest.swift in Sources */,
1F4A566D1A3B3159009E1637 /* ObjCBeKindOfTest.m in Sources */,
DD72EC641A93874A002F7651 /* AllPassTest.swift in Sources */,
1F4A569D1A3B3565009E1637 /* ObjCMatchTest.m in Sources */,
1F925EE9195C124400ED456B /* BeAnInstanceOfTest.swift in Sources */,
1F4A566A1A3B3108009E1637 /* ObjCBeAnInstanceOfTest.m in Sources */,
Expand All @@ -866,6 +881,7 @@
1FD8CD511968AB07008ED995 /* BeLogical.swift in Sources */,
1FD8CD671968AB07008ED995 /* NMBExceptionCapture.m in Sources */,
DA9E8C831A414BB9002633C2 /* DSL+Wait.swift in Sources */,
DDB1BC7A1A92235600F743C3 /* AllPass.swift in Sources */,
1FD8CD3F1968AB07008ED995 /* BeAKindOf.swift in Sources */,
1FD8CD2F1968AB07008ED995 /* AssertionRecorder.swift in Sources */,
DDB4D5EE19FE43C200E9D9FE /* Match.swift in Sources */,
Expand Down Expand Up @@ -929,6 +945,7 @@
1F925EF7195C147800ED456B /* BeCloseToTest.swift in Sources */,
1F4A567A1A3B32E3009E1637 /* ObjCBeGreaterThanOrEqualToTest.m in Sources */,
1F4A568C1A3B3407009E1637 /* ObjCBeTrueTest.m in Sources */,
DDEFAEB51A93CBE6005CA37A /* ObjCAllPassTest.m in Sources */,
1F4A56801A3B333F009E1637 /* ObjCBeLessThanTest.m in Sources */,
1F925EE7195C121200ED456B /* AsynchronousTest.swift in Sources */,
1F0648CD19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */,
Expand All @@ -938,6 +955,7 @@
1F925F09195C18CF00ED456B /* BeGreaterThanTest.swift in Sources */,
1F925F06195C18B700ED456B /* EqualTest.swift in Sources */,
1F4A566E1A3B3159009E1637 /* ObjCBeKindOfTest.m in Sources */,
DD72EC651A93874A002F7651 /* AllPassTest.swift in Sources */,
1F4A569E1A3B3565009E1637 /* ObjCMatchTest.m in Sources */,
1F925EEA195C124400ED456B /* BeAnInstanceOfTest.swift in Sources */,
1F4A566B1A3B3108009E1637 /* ObjCBeAnInstanceOfTest.m in Sources */,
Expand Down
99 changes: 99 additions & 0 deletions Nimble/Matchers/AllPass.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Foundation

public func allPass<T,U where U: SequenceType, U.Generator.Element == T>
(passFunc: (T?) -> Bool) -> NonNilMatcherFunc<U> {
return allPass("pass a condition", passFunc)
}

public func allPass<T,U where U: SequenceType, U.Generator.Element == T>
(passName:String, passFunc: (T?) -> Bool) -> NonNilMatcherFunc<U> {
return createAllPassMatcher() {
expression, failureMessage in
failureMessage.postfixMessage = passName
return passFunc(expression.evaluate())
}
}

public func allPass<U,V where U: SequenceType, V: NonNilBasicMatcher, U.Generator.Element == V.ValueType>
(matcher: V) -> NonNilMatcherFunc<U> {
let wrapper = NonNilMatcherWrapper(NonNilBasicMatcherWrapper(matcher))
return createAllPassMatcher() {wrapper.matches($0, failureMessage: $1)}
}

public func allPass<U,V where U: SequenceType, V: BasicMatcher, U.Generator.Element == V.ValueType>
(matcher: V) -> NonNilMatcherFunc<U> {
let wrapper = BasicMatcherWrapper(matcher: matcher)
return createAllPassMatcher() {wrapper.matches($0, failureMessage: $1)}
}

public func allPass<U,V where U: SequenceType, V: Matcher, U.Generator.Element == V.ValueType>
(matcher: V) -> NonNilMatcherFunc<U> {
return createAllPassMatcher() {matcher.matches($0, failureMessage: $1)}
}

private func createAllPassMatcher<T,U where U: SequenceType, U.Generator.Element == T>
(elementEvaluator:(Expression<T>, FailureMessage) -> Bool) -> NonNilMatcherFunc<U> {
return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.actualValue = nil
if let actualValue = actualExpression.evaluate() {
for currentElement in actualValue {
let exp = Expression(
expression: {currentElement}, location: actualExpression.location)
if !elementEvaluator(exp, failureMessage) {
failureMessage.postfixMessage =
"all \(failureMessage.postfixMessage),"
+ " but failed first at element <\(stringify(currentElement))>"
+ " in <\(stringify(actualValue))>"
return false
}
}
failureMessage.postfixMessage = "all \(failureMessage.postfixMessage)"
} else {
failureMessage.postfixMessage = "all pass (use beNil() to match nils)"
return false
}

return true
}
}

extension NMBObjCMatcher {
public class func allPassMatcher(matcher: NMBObjCMatcher) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage, location in
let actualValue = actualExpression.evaluate()
var nsObjects = [NSObject]()

var collectionIsUsable = true
if let value = actualValue as? NSFastEnumeration {
let generator = NSFastGenerator(value)
while let obj:AnyObject = generator.next() {
if let nsObject = obj as? NSObject {
nsObjects.append(nsObject)
} else {
collectionIsUsable = false
break
}
}
} else {
collectionIsUsable = false
}

if !collectionIsUsable {
failureMessage.postfixMessage =
"allPass only works with NSFastEnumeration (NSArray, NSSet, ...) of NSObjects"
failureMessage.expected = ""
failureMessage.to = ""
return false
}

let expr = Expression(expression: ({ nsObjects }), location: location)
let elementEvaluator: (Expression<NSObject>, FailureMessage) -> Bool = {
expression, failureMessage in
return matcher.matches(
{expression.evaluate()}, failureMessage: failureMessage, location: expr.location)
}
return createAllPassMatcher(elementEvaluator).matches(
expr, failureMessage: failureMessage)
}
}
}
4 changes: 4 additions & 0 deletions Nimble/objc/DSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ NIMBLE_EXPORT id<NMBMatcher> NMB_match(id expectedValue);
NIMBLE_SHORT(id<NMBMatcher> match(id expectedValue),
NMB_match(expectedValue));

NIMBLE_EXPORT id<NMBMatcher> NMB_allPass(id matcher);
NIMBLE_SHORT(id<NMBMatcher> allPass(id matcher),
NMB_allPass(matcher));

// In order to preserve breakpoint behavior despite using macros to fill in __FILE__ and __LINE__,
// define a builder that populates __FILE__ and __LINE__, and returns a block that takes timeout
// and action arguments. See https://github.com/Quick/Quick/pull/185 for details.
Expand Down
4 changes: 4 additions & 0 deletions Nimble/objc/DSL.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
return [NMBObjCMatcher matchMatcher:expectedValue];
}

NIMBLE_EXPORT id<NMBMatcher> NMB_allPass(id expectedValue) {
return [NMBObjCMatcher allPassMatcher:expectedValue];
}

NIMBLE_EXPORT NMBObjCRaiseExceptionMatcher *NMB_raiseException() {
return [NMBObjCMatcher raiseExceptionMatcher];
}
Expand Down
74 changes: 74 additions & 0 deletions NimbleTests/Matchers/AllPassTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import XCTest
import Nimble

class AllPassTest: XCTestCase {
func testAllPassArray() {
expect([1,2,3,4]).to(allPass({$0 < 5}))
expect([1,2,3,4]).toNot(allPass({$0 > 5}))

failsWithErrorMessage(
"expected to all pass a condition, but failed first at element <3> in <[1, 2, 3, 4]>") {
expect([1,2,3,4]).to(allPass({$0 < 3}))
}
failsWithErrorMessage("expected to not all pass a condition") {
expect([1,2,3,4]).toNot(allPass({$0 < 5}))
}
failsWithErrorMessage(
"expected to all be something, but failed first at element <3> in <[1, 2, 3, 4]>") {
expect([1,2,3,4]).to(allPass("be something", {$0 < 3}))
}
failsWithErrorMessage("expected to not all be something") {
expect([1,2,3,4]).toNot(allPass("be something", {$0 < 5}))
}
}

func testAllPassMatcher() {
expect([1,2,3,4]).to(allPass(beLessThan(5)))
expect([1,2,3,4]).toNot(allPass(beGreaterThan(5)))

failsWithErrorMessage(
"expected to all be less than <3>, but failed first at element <3> in <[1, 2, 3, 4]>") {
expect([1,2,3,4]).to(allPass(beLessThan(3)))
}
failsWithErrorMessage("expected to not all be less than <5>") {
expect([1,2,3,4]).toNot(allPass(beLessThan(5)))
}
}

func testAllPassCollectionsWithOptionalsDontWork() {
failsWithErrorMessage("expected to all be nil, but failed first at element <nil> in <[nil, nil, nil]>") {
expect([nil, nil, nil] as [Int?]).to(allPass(beNil()))
}
failsWithErrorMessage("expected to all pass a condition, but failed first at element <nil> in <[nil, nil, nil]>") {
expect([nil, nil, nil] as [Int?]).to(allPass({$0 == nil}))
}
}

func testAllPassCollectionsWithOptionalsUnwrappingOneOptionalLayer() {
expect([nil, nil, nil] as [Int?]).to(allPass({$0! == nil}))
expect([nil, 1, nil] as [Int?]).toNot(allPass({$0! == nil}))
expect([1, 1, 1] as [Int?]).to(allPass({$0! == 1}))
expect([1, 1, nil] as [Int?]).toNot(allPass({$0! == 1}))
expect([1, 2, 3] as [Int?]).to(allPass({$0! < 4}))
expect([1, 2, 3] as [Int?]).toNot(allPass({$0! < 3}))
expect([1, 2, nil] as [Int?]).to(allPass({$0! < 3}))
}

func testAllPassSet() {
expect(Set([1,2,3,4])).to(allPass({$0 < 5}))
expect(Set([1,2,3,4])).toNot(allPass({$0 > 5}))

failsWithErrorMessage("expected to not all pass a condition") {
expect(Set([1,2,3,4])).toNot(allPass({$0 < 5}))
}
failsWithErrorMessage("expected to not all be something") {
expect(Set([1,2,3,4])).toNot(allPass("be something", {$0 < 5}))
}
}

func testAllPassWithNilAsExpectedValue() {
failsWithErrorMessageForNil("expected to all pass") {
expect(nil as [Int]?).to(allPass(beLessThan(5)))
}
}
}
38 changes: 38 additions & 0 deletions NimbleTests/objc/ObjCAllPassTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#import <XCTest/XCTest.h>
#import "NimbleSpecHelper.h"

@interface ObjCAllPassTest : XCTestCase

@end

@implementation ObjCAllPassTest

- (void)testPositiveMatches {
expect(@[@1, @2, @3,@4]).to(allPass(beLessThan(@5)));
expect(@[@1, @2, @3,@4]).toNot(allPass(beGreaterThan(@5)));

expect([NSSet setWithArray:@[@1, @2, @3,@4]]).to(allPass(beLessThan(@5)));
expect([NSSet setWithArray:@[@1, @2, @3,@4]]).toNot(allPass(beGreaterThan(@5)));
}

- (void)testNegativeMatches {
expectFailureMessage(@"expected to all be less than <3.0000>, but failed first at element"
" <3.0000> in <[1.0000, 2.0000, 3.0000, 4.0000]>", ^{
expect(@[@1, @2, @3,@4]).to(allPass(beLessThan(@3)));
});
expectFailureMessage(@"expected to not all be less than <5.0000>", ^{
expect(@[@1, @2, @3,@4]).toNot(allPass(beLessThan(@5)));
});
expectFailureMessage(@"expected to not all be less than <5.0000>", ^{
expect([NSSet setWithArray:@[@1, @2, @3,@4]]).toNot(allPass(beLessThan(@5)));
});
expectFailureMessage(@"allPass only works with NSFastEnumeration"
" (NSArray, NSSet, ...) of NSObjects, got <3.0000>", ^{
expect(@3).to(allPass(beLessThan(@5)));
});
expectFailureMessage(@"allPass only works with NSFastEnumeration"
" (NSArray, NSSet, ...) of NSObjects, got <3.0000>", ^{
expect(@3).toNot(allPass(beLessThan(@5)));
});
}
@end
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ expect(ocean.isClean).toEventually(beTruthy())
- [Exceptions](#exceptions)
- [Collection Membership](#collection-membership)
- [Strings](#strings)
- [Checking if all elements of a collection pass a condition](#checking-if-all-elements-of-a-collection-pass-a-condition)
- [Writing Your Own Matchers](#writing-your-own-matchers)
- [Lazy Evaluation](#lazy-evaluation)
- [Type Checking via Swift Generics](#type-checking-via-swift-generics)
Expand Down Expand Up @@ -782,6 +783,29 @@ expect(actual).to(beEmpty());
expect(actual).to(match(expected))
```
## Checking if all elements of a collection pass a condition
```swift
// Swift
// with a custom function:
expect([1,2,3,4]).to(allPass({$0 < 5}))
// with another matcher:
expect([1,2,3,4]).to(allPass(beLessThan(5)))
```

```objc
// Objective-C

expect(@[@1, @2, @3,@4]).to(allPass(beLessThan(@5)));
```
For Swift the actual value has to be a SequenceType, e.g. an array, a set or a custom seqence type.
For Objective-C the actual value has to be a NSFastEnumeration, e.g. NSArray and NSSet, of NSObjects and only the variant which
uses another matcher is available here.
# Writing Your Own Matchers
In Nimble, matchers are Swift functions that take an expected
Expand Down

0 comments on commit 6a679cb

Please sign in to comment.