| // |
| // NSObjectRACLifting.m |
| // ReactiveCocoa |
| // |
| // Created by Josh Abernathy on 10/2/12. |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| // |
| |
| #import "RACTestObject.h" |
| |
| #import "NSObject+RACLifting.h" |
| #import "NSObject+RACDeallocating.h" |
| #import "NSObject+RACPropertySubscribing.h" |
| #import "RACCompoundDisposable.h" |
| #import "RACDisposable.h" |
| #import "RACSubject.h" |
| #import "RACTuple.h" |
| #import "RACUnit.h" |
| |
| SpecBegin(NSObjectRACLifting) |
| |
| describe(@"-rac_liftSelector:withSignals:", ^{ |
| __block RACTestObject *object; |
| |
| beforeEach(^{ |
| object = [[RACTestObject alloc] init]; |
| }); |
| |
| it(@"should call the selector with the value of the signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:) withSignals:subject, nil]; |
| |
| expect(object.objectValue).to.beNil(); |
| |
| [subject sendNext:@1]; |
| expect(object.objectValue).to.equal(@1); |
| |
| [subject sendNext:@42]; |
| expect(object.objectValue).to.equal(@42); |
| }); |
| }); |
| |
| describe(@"-rac_liftSelector:withSignalsFromArray:", ^{ |
| __block RACTestObject *object; |
| |
| beforeEach(^{ |
| object = [[RACTestObject alloc] init]; |
| }); |
| |
| it(@"should call the selector with the value of the signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.objectValue).to.beNil(); |
| |
| [subject sendNext:@1]; |
| expect(object.objectValue).to.equal(@1); |
| |
| [subject sendNext:@42]; |
| expect(object.objectValue).to.equal(@42); |
| }); |
| |
| it(@"should call the selector with the value of the signal unboxed", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.integerValue).to.equal(0); |
| |
| [subject sendNext:@1]; |
| expect(object.integerValue).to.equal(1); |
| |
| [subject sendNext:@42]; |
| expect(object.integerValue).to.equal(42); |
| }); |
| |
| it(@"should work with multiple arguments", ^{ |
| RACSubject *objectValueSubject = [RACSubject subject]; |
| RACSubject *integerValueSubject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectValueSubject, integerValueSubject ]]; |
| |
| expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beFalsy(); |
| expect(object.objectValue).to.beNil(); |
| expect(object.integerValue).to.equal(0); |
| |
| [objectValueSubject sendNext:@1]; |
| expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beFalsy(); |
| expect(object.objectValue).to.beNil(); |
| expect(object.integerValue).to.equal(0); |
| |
| [integerValueSubject sendNext:@42]; |
| expect(object.hasInvokedSetObjectValueAndIntegerValue).to.beTruthy(); |
| expect(object.objectValue).to.equal(@1); |
| expect(object.integerValue).to.equal(42); |
| }); |
| |
| it(@"should work with signals that immediately start with a value", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ [subject startWith:@42] ]]; |
| |
| expect(object.objectValue).to.equal(@42); |
| |
| [subject sendNext:@1]; |
| expect(object.objectValue).to.equal(@1); |
| }); |
| |
| it(@"should work with signals that send nil", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; |
| |
| [subject sendNext:nil]; |
| expect(object.objectValue).to.equal(nil); |
| |
| [subject sendNext:RACTupleNil.tupleNil]; |
| expect(object.objectValue).to.equal(nil); |
| }); |
| |
| it(@"should work with integers", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.integerValue).to.equal(0); |
| |
| [subject sendNext:@1]; |
| expect(object.integerValue).to.equal(@1); |
| }); |
| |
| it(@"should convert between numeric types", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setIntegerValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.integerValue).to.equal(0); |
| |
| [subject sendNext:@1.0]; |
| expect(object.integerValue).to.equal(@1); |
| }); |
| |
| it(@"should work with class objects", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.objectValue).to.equal(nil); |
| |
| [subject sendNext:self.class]; |
| expect(object.objectValue).to.equal(self.class); |
| }); |
| |
| it(@"should work for char pointer", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setCharPointerValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.charPointerValue).to.equal(NULL); |
| |
| NSString *string = @"blah blah blah"; |
| [subject sendNext:string]; |
| expect(@(object.charPointerValue)).to.equal(string); |
| }); |
| |
| it(@"should work for const char pointer", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setConstCharPointerValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.constCharPointerValue).to.equal(NULL); |
| |
| NSString *string = @"blah blah blah"; |
| [subject sendNext:string]; |
| expect(@(object.constCharPointerValue)).to.equal(string); |
| }); |
| |
| it(@"should work for CGRect", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setRectValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.rectValue).to.equal(CGRectZero); |
| |
| CGRect value = CGRectMake(10, 20, 30, 40); |
| [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGRect)]]; |
| expect(object.rectValue).to.equal(value); |
| }); |
| |
| it(@"should work for CGSize", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setSizeValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.sizeValue).to.equal(CGSizeZero); |
| |
| CGSize value = CGSizeMake(10, 20); |
| [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGSize)]]; |
| expect(object.sizeValue).to.equal(value); |
| }); |
| |
| it(@"should work for CGPoint", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setPointValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.pointValue).to.equal(CGPointZero); |
| |
| CGPoint value = CGPointMake(10, 20); |
| [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(CGPoint)]]; |
| expect(object.pointValue).to.equal(value); |
| }); |
| |
| it(@"should work for NSRange", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setRangeValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(NSEqualRanges(object.rangeValue, NSMakeRange(0, 0))).to.beTruthy(); |
| |
| NSRange value = NSMakeRange(10, 20); |
| [subject sendNext:[NSValue valueWithRange:value]]; |
| expect(NSEqualRanges(object.rangeValue, value)).to.beTruthy(); |
| }); |
| |
| it(@"should work for _Bool", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setC99BoolValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.c99BoolValue).to.beFalsy(); |
| |
| _Bool value = true; |
| [subject sendNext:@(value)]; |
| expect(object.c99BoolValue).to.beTruthy(); |
| }); |
| |
| it(@"should work for primitive pointers", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(write5ToIntPointer:) withSignalsFromArray:@[ subject ]]; |
| |
| int value = 0; |
| int *valuePointer = &value; |
| expect(value).to.equal(0); |
| |
| [subject sendNext:[NSValue valueWithPointer:valuePointer]]; |
| expect(value).to.equal(5); |
| }); |
| |
| it(@"should work for custom structs", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setStructValue:) withSignalsFromArray:@[ subject ]]; |
| |
| expect(object.structValue.integerField).to.equal(0); |
| expect(object.structValue.doubleField).to.equal(0.0); |
| |
| RACTestStruct value = (RACTestStruct){7, 1.23}; |
| [subject sendNext:[NSValue valueWithBytes:&value objCType:@encode(typeof(value))]]; |
| expect(object.structValue.integerField).to.equal(value.integerField); |
| expect(object.structValue.doubleField).to.equal(value.doubleField); |
| }); |
| |
| it(@"should send the latest value of the signal as the right argument", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| [object rac_liftSelector:@selector(setObjectValue:andIntegerValue:) withSignalsFromArray:@[ [RACSignal return:@"object"], subject ]]; |
| [subject sendNext:@1]; |
| |
| expect(object.objectValue).to.equal(@"object"); |
| expect(object.integerValue).to.equal(1); |
| }); |
| |
| describe(@"the returned signal", ^{ |
| it(@"should send the return value of the method invocation", ^{ |
| RACSubject *objectSubject = [RACSubject subject]; |
| RACSubject *integerSubject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; |
| |
| __block NSString *result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [objectSubject sendNext:@"Magic number"]; |
| expect(result).to.beNil(); |
| |
| [integerSubject sendNext:@42]; |
| expect(result).to.equal(@"Magic number: 42"); |
| }); |
| |
| it(@"should send RACUnit.defaultUnit for void-returning methods", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; |
| |
| __block id result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [subject sendNext:@1]; |
| |
| expect(result).to.equal(RACUnit.defaultUnit); |
| }); |
| |
| it(@"should support integer returning methods", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(doubleInteger:) withSignalsFromArray:@[ subject ]]; |
| |
| __block id result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [subject sendNext:@1]; |
| |
| expect(result).to.equal(@2); |
| }); |
| |
| it(@"should support char * returning methods", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(doubleString:) withSignalsFromArray:@[ subject ]]; |
| |
| __block id result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [subject sendNext:@"test"]; |
| |
| expect(result).to.equal(@"testtest"); |
| }); |
| |
| it(@"should support const char * returning methods", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(doubleConstString:) withSignalsFromArray:@[ subject ]]; |
| |
| __block id result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [subject sendNext:@"test"]; |
| |
| expect(result).to.equal(@"testtest"); |
| }); |
| |
| it(@"should support struct returning methods", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(doubleStruct:) withSignalsFromArray:@[ subject ]]; |
| |
| __block NSValue *boxedResult; |
| [signal subscribeNext:^(id x) { |
| boxedResult = x; |
| }]; |
| |
| RACTestStruct value = {4, 12.3}; |
| NSValue *boxedValue = [NSValue valueWithBytes:&value objCType:@encode(typeof(value))]; |
| [subject sendNext:boxedValue]; |
| |
| RACTestStruct result = {0, 0.0}; |
| [boxedResult getValue:&result]; |
| expect(result.integerField).to.equal(8); |
| expect(result.doubleField).to.equal(24.6); |
| }); |
| |
| it(@"should support block arguments and returns", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(wrapBlock:) withSignalsFromArray:@[ subject ]]; |
| |
| __block BOOL blockInvoked = NO; |
| dispatch_block_t testBlock = ^{ |
| blockInvoked = YES; |
| }; |
| |
| __block dispatch_block_t result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| [subject sendNext:testBlock]; |
| expect(result).notTo.beNil(); |
| |
| result(); |
| expect(blockInvoked).to.beTruthy(); |
| }); |
| |
| it(@"should replay the last value", ^{ |
| RACSubject *objectSubject = [RACSubject subject]; |
| RACSubject *integerSubject = [RACSubject subject]; |
| RACSignal *signal = [object rac_liftSelector:@selector(combineObjectValue:andIntegerValue:) withSignalsFromArray:@[ objectSubject, integerSubject ]]; |
| |
| [objectSubject sendNext:@"Magic number"]; |
| [integerSubject sendNext:@42]; |
| [integerSubject sendNext:@43]; |
| |
| __block NSString *result; |
| [signal subscribeNext:^(id x) { |
| result = x; |
| }]; |
| |
| expect(result).to.equal(@"Magic number: 43"); |
| }); |
| }); |
| |
| it(@"shouldn't strongly capture the receiver", ^{ |
| __block BOOL dealloced = NO; |
| @autoreleasepool { |
| RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; |
| [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| dealloced = YES; |
| }]]; |
| |
| RACSubject *subject = [RACSubject subject]; |
| [testObject rac_liftSelector:@selector(setObjectValue:) withSignalsFromArray:@[ subject ]]; |
| [subject sendNext:@1]; |
| } |
| |
| expect(dealloced).to.beTruthy(); |
| }); |
| }); |
| |
| SpecEnd |