Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions Cedar.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions Source/Extensions/NSArray+ExplicitDescription.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "NSArray+ExplicitDescription.h"

@implementation NSArray (ExplicitDescription)

- (NSString *)cdr_explicitDescription {
NSMutableArray *elementDescriptions = [NSMutableArray arrayWithCapacity:self.count];
for (id element in self) {
if ([element respondsToSelector:@selector(cdr_explicitDescription)]) {
NSString *indentedDescription = [[element cdr_explicitDescription] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[elementDescriptions addObject:[@" " stringByAppendingString:indentedDescription]];
} else {
[elementDescriptions addObject:[NSString stringWithFormat:@" %@ (%@)", element, [element class]]];
}
}
return [NSString stringWithFormat:@"(\n%@\n)", [elementDescriptions componentsJoinedByString:@",\n"]];
}

@end
30 changes: 30 additions & 0 deletions Source/Extensions/NSDictionary+ExplicitDescription.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#import "NSDictionary+ExplicitDescription.h"

@implementation NSDictionary (ExplicitDescription)

- (NSString *)cdr_explicitDescription {
NSMutableArray *elementDescriptions = [NSMutableArray arrayWithCapacity:self.count];
for (id key in self) {
NSMutableString *elementDescription = [NSMutableString string];
if ([key respondsToSelector:@selector(cdr_explicitDescription)]) {
NSString *indentedKeyDescription = [[key cdr_explicitDescription] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[elementDescription appendString:@" "];
[elementDescription appendString:indentedKeyDescription];
[elementDescription appendString:@" = "];
} else {
[elementDescription appendFormat:@" %@ (%@) = ", key, [key class]];
}

id element = self[key];
if ([element respondsToSelector:@selector(cdr_explicitDescription)]) {
NSString *indentedDescription = [[element cdr_explicitDescription] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[elementDescription appendString:indentedDescription];
} else {
[elementDescription appendFormat:@"%@ (%@)", element, [element class]];
}
[elementDescriptions addObject:elementDescription];
}
return [NSString stringWithFormat:@"{\n%@\n}", [elementDescriptions componentsJoinedByString:@",\n"]];
}

@end
18 changes: 18 additions & 0 deletions Source/Extensions/NSOrderedSet+ExplicitDescription.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "NSOrderedSet+ExplicitDescription.h"

@implementation NSOrderedSet (ExplicitDescription)

- (NSString *)cdr_explicitDescription {
NSMutableArray *elementDescriptions = [NSMutableArray arrayWithCapacity:self.count];
for (id element in self) {
if ([element respondsToSelector:@selector(cdr_explicitDescription)]) {
NSString *indentedDescription = [[element cdr_explicitDescription] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[elementDescriptions addObject:[@" " stringByAppendingString:indentedDescription]];
} else {
[elementDescriptions addObject:[NSString stringWithFormat:@" %@ (%@)", element, [element class]]];
}
}
return [NSString stringWithFormat:@"{(\n%@\n)}", [elementDescriptions componentsJoinedByString:@",\n"]];
}

@end
18 changes: 18 additions & 0 deletions Source/Extensions/NSSet+ExplicitDescription.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "NSSet+ExplicitDescription.h"

@implementation NSSet (ExplicitDescription)

- (NSString *)cdr_explicitDescription {
NSMutableArray *elementDescriptions = [NSMutableArray arrayWithCapacity:self.count];
for (id element in self) {
if ([element respondsToSelector:@selector(cdr_explicitDescription)]) {
NSString *indentedDescription = [[element cdr_explicitDescription] stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[elementDescriptions addObject:[@" " stringByAppendingString:indentedDescription]];
} else {
[elementDescriptions addObject:[NSString stringWithFormat:@" %@ (%@)", element, [element class]]];
}
}
return [NSString stringWithFormat:@"{(\n%@\n)}", [elementDescriptions componentsJoinedByString:@",\n"]];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@interface NSArray (ExplicitDescription)
- (NSString *)cdr_explicitDescription;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@interface NSDictionary (ExplicitDescription)
- (NSString *)cdr_explicitDescription;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@interface NSOrderedSet (ExplicitDescription)
- (NSString *)cdr_explicitDescription;
@end
5 changes: 5 additions & 0 deletions Source/Headers/Project/Extensions/NSSet+ExplicitDescription.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@interface NSSet (ExplicitDescription)
- (NSString *)cdr_explicitDescription;
@end
14 changes: 14 additions & 0 deletions Source/Headers/Public/Matchers/Base/Equal.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace Cedar { namespace Matchers { namespace Private {
template<typename U>
bool matches(const U &) const;

template<typename U>
NSString * failure_message_for(const U &) const;

protected:
virtual NSString * failure_message_end() const;

Expand All @@ -37,6 +40,17 @@ namespace Cedar { namespace Matchers { namespace Private {
Equal<T>::~Equal() {
}

template<typename T> template<typename U>
NSString * Equal<T>::failure_message_for(const U & value) const {
if (0 == strncmp(@encode(T), "@", 1) &&
0 == strncmp(@encode(U), "@", 1) &&
[Stringifiers::string_for(expectedValue_) isEqualToString:Stringifiers::string_for(value)]) {
Stringifiers::attempt_future_explication(&expectedValue_);
Stringifiers::attempt_future_explication(&value);
}
return Base<>::failure_message_for(value);
}

template<typename T>
/*virtual*/ NSString * Equal<T>::failure_message_end() const {
NSString * expectedValueString = Stringifiers::string_for(expectedValue_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Cedar { namespace Matchers { namespace Stringifiers {
NSString * object_description_for(const void *objectValue);
void attempt_future_explication(const void *objectValue);

template<typename U>
NSString * string_for(const U & value) {
Expand Down
19 changes: 18 additions & 1 deletion Source/Matchers/Stringifiers/StringifiersBase.mm
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
#import "StringifiersBase.h"
#import <objc/runtime.h>

@protocol CDRExplicitDescription //informal
- (NSString *)cdr_explicitDescription;
@end

static char *CDRUseExplicitDescriptionKey;

namespace Cedar { namespace Matchers { namespace Stringifiers {
NSString * object_description_for(const void *objectValue) {
NSValue *valueId = [NSValue valueWithBytes:objectValue objCType:@encode(id)];
id object = [valueId nonretainedObjectValue];
Class klass = object_getClass(object);
if (object && class_getInstanceMethod(klass, @selector(description)) == NULL) {
if ([objc_getAssociatedObject(object, &CDRUseExplicitDescriptionKey) boolValue]) {
return [NSString stringWithFormat:@"%@", [[valueId nonretainedObjectValue] cdr_explicitDescription]];
} else if (object && class_getInstanceMethod(klass, @selector(description)) == NULL) {
return [NSString stringWithFormat:@"%@ %p", NSStringFromClass(klass), object];
} else {
return [NSString stringWithFormat:@"%@", [[valueId nonretainedObjectValue] description]];
}
}

void attempt_future_explication(const void *objectValue) {
NSValue *valueId = [NSValue valueWithBytes:objectValue objCType:@encode(id)];
id object = [valueId nonretainedObjectValue];
Class klass = object_getClass(object);
if (class_getInstanceMethod(klass, @selector(cdr_explicitDescription))) {
objc_setAssociatedObject(object, &CDRUseExplicitDescriptionKey, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
}}}
112 changes: 112 additions & 0 deletions Spec/Extensions/CollectionsExplicitDescriptionSpec.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#import <Cedar/Cedar.h>
#import "NSArray+ExplicitDescription.h"
#import "NSDictionary+ExplicitDescription.h"
#import "NSSet+ExplicitDescription.h"
#import "NSOrderedSet+ExplicitDescription.h"

using namespace Cedar::Matchers;
using namespace Cedar::Doubles;

SPEC_BEGIN(CollectionsExplicitDescriptionSpec)

describe(@"Explicit Descriptions on Collection Types", ^{
describe(@"NSArray", ^{
NSArray *array = @[
@1,
@"two",
@[ @"three" ],
[NSSet setWithObject:@4],
@{ @"five": @6 },
[NSOrderedSet orderedSetWithObject:@"seven"]
];

it(@"should describe collections recursively, annotating non-collection types", ^{
[array cdr_explicitDescription] should equal(@"(\n"
@" 1 (__NSCFNumber),\n"
@" two (__NSCFConstantString),\n"
@" (\n"
@" three (__NSCFConstantString)\n"
@" ),\n"
@" {(\n"
@" 4 (__NSCFNumber)\n"
@" )},\n"
@" {\n"
@" five (__NSCFConstantString) = 6 (__NSCFNumber)\n"
@" },\n"
@" {(\n"
@" seven (__NSCFConstantString)\n"
@" )}\n"
@")");
});
});

describe(@"NSDictionary", ^{
NSDictionary *dictionary = @{
@"one": @2,
@[ @"three" ]: [NSSet setWithObject:@4],
[NSOrderedSet orderedSetWithObject:@"five"]: @{ @6: @"seven" }
};

it(@"should describe collections recursively, annotating non-collection types", ^{
[dictionary cdr_explicitDescription] should equal(@"{\n"
@" one (__NSCFConstantString) = 2 (__NSCFNumber),\n"
@" (\n"
@" three (__NSCFConstantString)\n"
@" ) = {(\n"
@" 4 (__NSCFNumber)\n"
@" )},\n"
@" {(\n"
@" five (__NSCFConstantString)\n"
@" )} = {\n"
@" 6 (__NSCFNumber) = seven (__NSCFConstantString)\n"
@" }\n"
@"}");
});
});

describe(@"NSOrderedSet", ^{
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithObjects:@"one", @2, @[ @"three" ], @{ @4: @"five" }, [NSSet setWithObject:@6], [NSOrderedSet orderedSetWithObject:@"seven"], nil];

it(@"should describe collections recursively, annotating non-collection types", ^{
[orderedSet cdr_explicitDescription] should equal(@"{(\n"
@" one (__NSCFConstantString),\n"
@" 2 (__NSCFNumber),\n"
@" (\n"
@" three (__NSCFConstantString)\n"
@" ),\n"
@" {\n"
@" 4 (__NSCFNumber) = five (__NSCFConstantString)\n"
@" },\n"
@" {(\n"
@" 6 (__NSCFNumber)\n"
@" )},\n"
@" {(\n"
@" seven (__NSCFConstantString)\n"
@" )}\n"
@")}");
});
});

describe(@"NSSet", ^{
NSSet *set = [NSSet setWithObjects:@"one", @2, @[ @"three" ], @{ @4: @"five" }, [NSSet setWithObject:@6], [NSOrderedSet orderedSetWithObject:@"seven"], nil];

it(@"should describe collections recursively, annotating non-collection types", ^{
NSArray *expectedElementDescriptions = @[@"{(\n",
@" one (__NSCFConstantString)",
@" 2 (__NSCFNumber)",
@" (\n three (__NSCFConstantString)\n )",
@" {\n 4 (__NSCFNumber) = five (__NSCFConstantString)\n }",
@" {(\n 6 (__NSCFNumber)\n )}",
@" {(\n seven (__NSCFConstantString)\n )}",
@")}"];
NSString *actualExplicitDescription = [set cdr_explicitDescription];
for (NSString *elementDescription in expectedElementDescriptions) {
actualExplicitDescription should contain(elementDescription);
}
NSInteger expectedNumberOfCommasAndNewLines = (2 * set.count) - 1;
actualExplicitDescription.length should equal([expectedElementDescriptions componentsJoinedByString:@""].length + expectedNumberOfCommasAndNewLines);
});
});
});

SPEC_END
29 changes: 29 additions & 0 deletions Spec/Matchers/Base/EqualSpec.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ - (NSString *)description {
}
@end

@interface CustomObjectWithExplicitDescription: CustomObject
@end

@implementation CustomObjectWithExplicitDescription
- (NSString *)cdr_explicitDescription {
return @"MoreExplicitCustomObject";
}
@end

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-root-class"
NS_ROOT_CLASS
Expand Down Expand Up @@ -1902,6 +1911,26 @@ - (BOOL)isEqual:(id)other {
});
});
});

describe(@"when two objects aren't equal but have the same description and implement cdr_explicitDescription", ^{
__block CustomObjectWithExplicitDescription *actualValue, *expectedValue;

beforeEach(^{
actualValue = [[CustomObjectWithExplicitDescription alloc] init];
expectedValue = [[CustomObjectWithExplicitDescription alloc] init];
actualValue.shouldEqual = expectedValue.shouldEqual = NO;
});

describe(@"positive match", ^{
it(@"should fail with a sensible failure message using the explicit description", ^{
expectFailureWithMessage(@"Expected <MoreExplicitCustomObject> to equal <MoreExplicitCustomObject>", ^{
expect(actualValue).to(equal(expectedValue));
});
});
});

});

});

#pragma clang diagnostic push
Expand Down