-
-
Notifications
You must be signed in to change notification settings - Fork 595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
All pass matcher #95
All pass matcher #95
Changes from all commits
4188ba9
e122c03
d67245b
92f3596
43013fa
d9a8ae8
8171f78
429edd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this if statement be inlined to where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately there are two situations at which the collection becomes unusable:
Inlining would double the same code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, my bad, I didn't see that, thanks for the pointing that out. |
||
|
||
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) | ||
} | ||
} | ||
} |
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))) | ||
} | ||
} | ||
} |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tangential to this pull request, but I recently created a matcher similar to this one: https://github.com/modocache/Guanaco/blob/18126c0c8ba9a7cb3cc5d43aec83c81047410ab6/Guanaco/HaveSucceeded.swift#L21-L49
I also had to write several matcher functions in order to handle
Matcher
,BasicMatcher
, andNonNilBasicMatcher
. Architecturally, this seems like a shortcoming. Any thoughts on improvements here, @jeffh?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there needs to be some changes to simplify the internals. I think the removal a BasicMatcher and NonNilBasicMatcher would be preferred in some way. There also needs a reinvestigation if the swift compiler still gives an obscure message when a generic does match optionals. having the matcher be able to specify that while Nimble's
Expectation
and matcher protocols be ignorant of that detail would be best.All that stuff are just floating in my head right now. For various personal reasons, I don't have the available free time yet.