blob: 7a2acfc3927efb41b870c6ad205bf015d0818e11 [file] [log] [blame]
//
// 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