| // |
| // RACSignalSpec.m |
| // ReactiveCocoa |
| // |
| // Created by Josh Abernathy on 3/2/12. |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. |
| // |
| |
| #import "RACPropertySignalExamples.h" |
| #import "RACSequenceExamples.h" |
| #import "RACStreamExamples.h" |
| #import "RACTestObject.h" |
| |
| #import "EXTKeyPathCoding.h" |
| #import "NSObject+RACDeallocating.h" |
| #import "NSObject+RACPropertySubscribing.h" |
| #import "RACBehaviorSubject.h" |
| #import "RACCommand.h" |
| #import "RACCompoundDisposable.h" |
| #import "RACDisposable.h" |
| #import "RACEvent.h" |
| #import "RACGroupedSignal.h" |
| #import "RACMulticastConnection.h" |
| #import "RACReplaySubject.h" |
| #import "RACScheduler.h" |
| #import "RACSignal+Operations.h" |
| #import "RACSignalStartExamples.h" |
| #import "RACSubject.h" |
| #import "RACSubscriber+Private.h" |
| #import "RACSubscriber.h" |
| #import "RACTestScheduler.h" |
| #import "RACTuple.h" |
| #import "RACUnit.h" |
| #import <libkern/OSAtomic.h> |
| |
| // Set in a beforeAll below. |
| static NSError *RACSignalTestError; |
| |
| static NSString * const RACSignalMergeConcurrentCompletionExampleGroup = @"RACSignalMergeConcurrentCompletionExampleGroup"; |
| static NSString * const RACSignalMaxConcurrent = @"RACSignalMaxConcurrent"; |
| SharedExampleGroupsBegin(mergeConcurrentCompletionName); |
| |
| sharedExamplesFor(RACSignalMergeConcurrentCompletionExampleGroup, ^(NSDictionary *data) { |
| it(@"should complete only after the source and all its signals have completed", ^{ |
| RACSubject *subject1 = [RACSubject subject]; |
| RACSubject *subject2 = [RACSubject subject]; |
| RACSubject *subject3 = [RACSubject subject]; |
| |
| RACSubject *signalsSubject = [RACSubject subject]; |
| __block BOOL completed = NO; |
| [[signalsSubject flatten:[data[RACSignalMaxConcurrent] unsignedIntegerValue]] subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| [signalsSubject sendNext:subject1]; |
| [subject1 sendCompleted]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [signalsSubject sendNext:subject2]; |
| [signalsSubject sendNext:subject3]; |
| |
| [signalsSubject sendCompleted]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject2 sendCompleted]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject3 sendCompleted]; |
| |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| SharedExampleGroupsEnd |
| |
| SpecBegin(RACSignal) |
| |
| beforeAll(^{ |
| // We do this instead of a macro to ensure that to.equal() will work |
| // correctly (by matching identity), even if -[NSError isEqual:] is broken. |
| RACSignalTestError = [NSError errorWithDomain:@"foo" code:100 userInfo:nil]; |
| }); |
| |
| describe(@"RACStream", ^{ |
| id verifyValues = ^(RACSignal *signal, NSArray *expectedValues) { |
| expect(signal).notTo.beNil(); |
| |
| NSMutableArray *collectedValues = [NSMutableArray array]; |
| |
| __block BOOL success = NO; |
| __block NSError *error = nil; |
| [signal subscribeNext:^(id value) { |
| [collectedValues addObject:value]; |
| } error:^(NSError *receivedError) { |
| error = receivedError; |
| } completed:^{ |
| success = YES; |
| }]; |
| |
| expect(success).will.beTruthy(); |
| expect(error).to.beNil(); |
| expect(collectedValues).to.equal(expectedValues); |
| }; |
| |
| RACSignal *infiniteSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| __block volatile int32_t done = 0; |
| |
| [RACScheduler.mainThreadScheduler schedule:^{ |
| while (!done) { |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| } |
| }]; |
| |
| return [RACDisposable disposableWithBlock:^{ |
| OSAtomicIncrement32Barrier(&done); |
| }]; |
| }]; |
| |
| itShouldBehaveLike(RACStreamExamples, ^{ |
| return @{ |
| RACStreamExamplesClass: RACSignal.class, |
| RACStreamExamplesVerifyValuesBlock: verifyValues, |
| RACStreamExamplesInfiniteStream: infiniteSignal |
| }; |
| }); |
| }); |
| |
| describe(@"-bind:", ^{ |
| __block RACSubject *signals; |
| __block BOOL disposed; |
| __block id lastValue; |
| __block RACSubject *values; |
| |
| beforeEach(^{ |
| // Tests send a (RACSignal, BOOL) pair that are used below in -bind:. |
| signals = [RACSubject subject]; |
| |
| disposed = NO; |
| RACSignal *source = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| [signals subscribe:subscriber]; |
| |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| |
| RACSignal *bind = [source bind:^{ |
| return ^(RACTuple *x, BOOL *stop) { |
| RACTupleUnpack(RACSignal *signal, NSNumber *stopValue) = x; |
| *stop = stopValue.boolValue; |
| return signal; |
| }; |
| }]; |
| |
| lastValue = nil; |
| [bind subscribeNext:^(id x) { |
| lastValue = x; |
| }]; |
| |
| // Send `bind` an open ended subject to subscribe to. These tests make |
| // use of this in two ways: |
| // 1. Used to test a regression bug where -bind: would not actually |
| // stop when instructed to. This bug manifested itself only when |
| // there were subscriptions that lived on past the point at which |
| // -bind: was stopped. This subject represents such a subscription. |
| // 2. Test that values sent by this subject are received by `bind`'s |
| // subscriber, even *after* -bind: has been instructed to stop. |
| values = [RACSubject subject]; |
| [signals sendNext:RACTuplePack(values, @NO)]; |
| expect(disposed).to.beFalsy(); |
| }); |
| |
| it(@"should dispose source signal when stopped with nil signal", ^{ |
| // Tell -bind: to stop by sending it a `nil` signal. |
| [signals sendNext:RACTuplePack(nil, @NO)]; |
| expect(disposed).to.beTruthy(); |
| |
| // Should still receive values sent after stopping. |
| expect(lastValue).to.beNil(); |
| [values sendNext:RACUnit.defaultUnit]; |
| expect(lastValue).to.equal(RACUnit.defaultUnit); |
| }); |
| |
| it(@"should dispose source signal when stop flag set to YES", ^{ |
| // Tell -bind: to stop by setting the stop flag to YES. |
| [signals sendNext:RACTuplePack([RACSignal return:@1], @YES)]; |
| expect(disposed).to.beTruthy(); |
| |
| // Should still recieve last signal sent at the time of setting stop to YES. |
| expect(lastValue).to.equal(@1); |
| |
| // Should still receive values sent after stopping. |
| [values sendNext:@2]; |
| expect(lastValue).to.equal(@2); |
| }); |
| }); |
| |
| describe(@"subscribing", ^{ |
| __block RACSignal *signal = nil; |
| id nextValueSent = @"1"; |
| |
| beforeEach(^{ |
| signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:nextValueSent]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| }); |
| |
| it(@"should get next values", ^{ |
| __block id nextValueReceived = nil; |
| [signal subscribeNext:^(id x) { |
| nextValueReceived = x; |
| } error:^(NSError *error) { |
| |
| } completed:^{ |
| |
| }]; |
| |
| expect(nextValueReceived).to.equal(nextValueSent); |
| }); |
| |
| it(@"should get completed", ^{ |
| __block BOOL didGetCompleted = NO; |
| [signal subscribeNext:^(id x) { |
| |
| } error:^(NSError *error) { |
| |
| } completed:^{ |
| didGetCompleted = YES; |
| }]; |
| |
| expect(didGetCompleted).to.beTruthy(); |
| }); |
| |
| it(@"should not get an error", ^{ |
| __block BOOL didGetError = NO; |
| [signal subscribeNext:^(id x) { |
| |
| } error:^(NSError *error) { |
| didGetError = YES; |
| } completed:^{ |
| |
| }]; |
| |
| expect(didGetError).to.beFalsy(); |
| }); |
| |
| it(@"shouldn't get anything after dispose", ^{ |
| RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; |
| NSMutableArray *receivedValues = [NSMutableArray array]; |
| |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@0]; |
| |
| [scheduler afterDelay:0 schedule:^{ |
| [subscriber sendNext:@1]; |
| }]; |
| |
| return nil; |
| }]; |
| |
| RACDisposable *disposable = [signal subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| NSArray *expectedValues = @[ @0 ]; |
| expect(receivedValues).to.equal(expectedValues); |
| |
| [disposable dispose]; |
| [scheduler stepAll]; |
| |
| expect(receivedValues).to.equal(expectedValues); |
| }); |
| |
| it(@"should have a current scheduler in didSubscribe block", ^{ |
| __block RACScheduler *currentScheduler; |
| RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| currentScheduler = RACScheduler.currentScheduler; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| [signal subscribeNext:^(id x) {}]; |
| expect(currentScheduler).notTo.beNil(); |
| |
| currentScheduler = nil; |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
| [signal subscribeNext:^(id x) {}]; |
| }); |
| expect(currentScheduler).willNot.beNil(); |
| }); |
| |
| it(@"should automatically dispose of other subscriptions from +createSignal:", ^{ |
| __block BOOL innerDisposed = NO; |
| |
| RACSignal *innerSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| innerDisposed = YES; |
| }]; |
| }]; |
| |
| RACSignal *outerSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [innerSignal subscribe:subscriber]; |
| return nil; |
| }]; |
| |
| RACDisposable *disposable = [outerSignal subscribeCompleted:^{}]; |
| expect(disposable).notTo.beNil(); |
| expect(innerDisposed).to.beFalsy(); |
| |
| [disposable dispose]; |
| expect(innerDisposed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-takeUntil:", ^{ |
| it(@"should support value as trigger", ^{ |
| __block BOOL shouldBeGettingItems = YES; |
| RACSubject *subject = [RACSubject subject]; |
| RACSubject *cutOffSubject = [RACSubject subject]; |
| [[subject takeUntil:cutOffSubject] subscribeNext:^(id x) { |
| expect(shouldBeGettingItems).to.beTruthy(); |
| }]; |
| |
| shouldBeGettingItems = YES; |
| [subject sendNext:@"test 1"]; |
| [subject sendNext:@"test 2"]; |
| |
| [cutOffSubject sendNext:[RACUnit defaultUnit]]; |
| |
| shouldBeGettingItems = NO; |
| [subject sendNext:@"test 3"]; |
| }); |
| |
| it(@"should support completion as trigger", ^{ |
| __block BOOL shouldBeGettingItems = YES; |
| RACSubject *subject = [RACSubject subject]; |
| RACSubject *cutOffSubject = [RACSubject subject]; |
| [[subject takeUntil:cutOffSubject] subscribeNext:^(id x) { |
| expect(shouldBeGettingItems).to.beTruthy(); |
| }]; |
| |
| [cutOffSubject sendCompleted]; |
| |
| shouldBeGettingItems = NO; |
| [subject sendNext:@"should not go through"]; |
| }); |
| |
| it(@"should squelch any values sent immediately upon subscription", ^{ |
| RACSignal *valueSignal = [RACSignal return:RACUnit.defaultUnit]; |
| RACSignal *cutOffSignal = [RACSignal empty]; |
| |
| __block BOOL gotNext = NO; |
| __block BOOL completed = NO; |
| |
| [[valueSignal takeUntil:cutOffSignal] subscribeNext:^(id _) { |
| gotNext = YES; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| expect(gotNext).to.beFalsy(); |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-takeUntilReplacement:", ^{ |
| it(@"should forward values from the receiver until it's replaced", ^{ |
| RACSubject *receiver = [RACSubject subject]; |
| RACSubject *replacement = [RACSubject subject]; |
| |
| NSMutableArray *receivedValues = [NSMutableArray array]; |
| |
| [[receiver takeUntilReplacement:replacement] subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| expect(receivedValues).to.equal(@[]); |
| |
| [receiver sendNext:@1]; |
| expect(receivedValues).to.equal(@[ @1 ]); |
| |
| [receiver sendNext:@2]; |
| expect(receivedValues).to.equal((@[ @1, @2 ])); |
| |
| [replacement sendNext:@3]; |
| expect(receivedValues).to.equal((@[ @1, @2, @3 ])); |
| |
| [receiver sendNext:@4]; |
| expect(receivedValues).to.equal((@[ @1, @2, @3 ])); |
| |
| [replacement sendNext:@5]; |
| expect(receivedValues).to.equal((@[ @1, @2, @3, @5 ])); |
| }); |
| |
| it(@"should forward error from the receiver", ^{ |
| RACSubject *receiver = [RACSubject subject]; |
| __block BOOL receivedError = NO; |
| |
| [[receiver takeUntilReplacement:RACSignal.never] subscribeError:^(NSError *error) { |
| receivedError = YES; |
| }]; |
| |
| [receiver sendError:nil]; |
| expect(receivedError).to.beTruthy(); |
| }); |
| |
| it(@"should not forward completed from the receiver", ^{ |
| RACSubject *receiver = [RACSubject subject]; |
| __block BOOL receivedCompleted = NO; |
| |
| [[receiver takeUntilReplacement:RACSignal.never] subscribeCompleted: ^{ |
| receivedCompleted = YES; |
| }]; |
| |
| [receiver sendCompleted]; |
| expect(receivedCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should forward error from the replacement signal", ^{ |
| RACSubject *replacement = [RACSubject subject]; |
| __block BOOL receivedError = NO; |
| |
| [[RACSignal.never takeUntilReplacement:replacement] subscribeError:^(NSError *error) { |
| receivedError = YES; |
| }]; |
| |
| [replacement sendError:nil]; |
| expect(receivedError).to.beTruthy(); |
| }); |
| |
| it(@"should forward completed from the replacement signal", ^{ |
| RACSubject *replacement = [RACSubject subject]; |
| __block BOOL receivedCompleted = NO; |
| |
| [[RACSignal.never takeUntilReplacement:replacement] subscribeCompleted: ^{ |
| receivedCompleted = YES; |
| }]; |
| |
| [replacement sendCompleted]; |
| expect(receivedCompleted).to.beTruthy(); |
| }); |
| |
| it(@"should not forward values from the receiver if both send synchronously", ^{ |
| RACSignal *receiver = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| [subscriber sendNext:@3]; |
| return nil; |
| }]; |
| RACSignal *replacement = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@4]; |
| [subscriber sendNext:@5]; |
| [subscriber sendNext:@6]; |
| return nil; |
| }]; |
| |
| NSMutableArray *receivedValues = [NSMutableArray array]; |
| |
| [[receiver takeUntilReplacement:replacement] subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| expect(receivedValues).to.equal((@[ @4, @5, @6 ])); |
| }); |
| |
| it(@"should dispose of the receiver when it's disposed of", ^{ |
| __block BOOL receiverDisposed = NO; |
| RACSignal *receiver = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| receiverDisposed = YES; |
| }]; |
| }]; |
| |
| [[[receiver takeUntilReplacement:RACSignal.never] subscribeCompleted:^{}] dispose]; |
| |
| expect(receiverDisposed).to.beTruthy(); |
| }); |
| |
| it(@"should dispose of the replacement signal when it's disposed of", ^{ |
| __block BOOL replacementDisposed = NO; |
| RACSignal *replacement = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| replacementDisposed = YES; |
| }]; |
| }]; |
| |
| [[[RACSignal.never takeUntilReplacement:replacement] subscribeCompleted:^{}] dispose]; |
| |
| expect(replacementDisposed).to.beTruthy(); |
| }); |
| |
| it(@"should dispose of the receiver when the replacement signal sends an event", ^{ |
| __block BOOL receiverDisposed = NO; |
| RACSignal *receiver = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| receiverDisposed = YES; |
| }]; |
| }]; |
| RACSubject *replacement = [RACSubject subject]; |
| |
| [[receiver takeUntilReplacement:replacement] subscribeCompleted:^{}]; |
| |
| expect(receiverDisposed).to.beFalsy(); |
| |
| [replacement sendNext:nil]; |
| |
| expect(receiverDisposed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"disposal", ^{ |
| it(@"should dispose of the didSubscribe disposable", ^{ |
| __block BOOL innerDisposed = NO; |
| RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| innerDisposed = YES; |
| }]; |
| }]; |
| |
| expect(innerDisposed).to.beFalsy(); |
| |
| RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; |
| expect(disposable).notTo.beNil(); |
| |
| [disposable dispose]; |
| expect(innerDisposed).to.beTruthy(); |
| }); |
| |
| it(@"should dispose of the didSubscribe disposable asynchronously", ^{ |
| __block BOOL innerDisposed = NO; |
| RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| innerDisposed = YES; |
| }]; |
| }]; |
| |
| [[RACScheduler scheduler] schedule:^{ |
| RACDisposable *disposable = [signal subscribeNext:^(id x) {}]; |
| [disposable dispose]; |
| }]; |
| |
| expect(innerDisposed).will.beTruthy(); |
| }); |
| }); |
| |
| describe(@"querying", ^{ |
| __block RACSignal *signal = nil; |
| id nextValueSent = @"1"; |
| |
| beforeEach(^{ |
| signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:nextValueSent]; |
| [subscriber sendNext:@"other value"]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| }); |
| |
| it(@"should return first 'next' value with -firstOrDefault:success:error:", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| [subscriber sendNext:@3]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| |
| __block BOOL success = NO; |
| __block NSError *error = nil; |
| expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@1); |
| expect(success).to.beTruthy(); |
| expect(error).to.beNil(); |
| }); |
| |
| it(@"should return first default value with -firstOrDefault:success:error:", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| |
| __block BOOL success = NO; |
| __block NSError *error = nil; |
| expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); |
| expect(success).to.beTruthy(); |
| expect(error).to.beNil(); |
| }); |
| |
| it(@"should return error with -firstOrDefault:success:error:", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendError:RACSignalTestError]; |
| return nil; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| |
| __block BOOL success = NO; |
| __block NSError *error = nil; |
| expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); |
| expect(success).to.beFalsy(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"shouldn't crash when returning an error from a background scheduler", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [[RACScheduler scheduler] schedule:^{ |
| [subscriber sendError:RACSignalTestError]; |
| }]; |
| |
| return nil; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| |
| __block BOOL success = NO; |
| __block NSError *error = nil; |
| expect([signal firstOrDefault:@5 success:&success error:&error]).to.equal(@5); |
| expect(success).to.beFalsy(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should terminate the subscription after returning from -firstOrDefault:success:error:", ^{ |
| __block BOOL disposed = NO; |
| RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| expect(disposed).to.beFalsy(); |
| |
| expect([signal firstOrDefault:nil success:NULL error:NULL]).to.equal(RACUnit.defaultUnit); |
| expect(disposed).to.beTruthy(); |
| }); |
| |
| it(@"should return YES from -waitUntilCompleted: when successful", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| __block NSError *error = nil; |
| expect([signal waitUntilCompleted:&error]).to.beTruthy(); |
| expect(error).to.beNil(); |
| }); |
| |
| it(@"should return NO from -waitUntilCompleted: upon error", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| [subscriber sendError:RACSignalTestError]; |
| return nil; |
| }]; |
| |
| __block NSError *error = nil; |
| expect([signal waitUntilCompleted:&error]).to.beFalsy(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should return a delayed value from -asynchronousFirstOrDefault:success:error:", ^{ |
| RACSignal *signal = [[RACSignal return:RACUnit.defaultUnit] delay:0]; |
| |
| __block BOOL scheduledBlockRan = NO; |
| [RACScheduler.mainThreadScheduler schedule:^{ |
| scheduledBlockRan = YES; |
| }]; |
| |
| expect(scheduledBlockRan).to.beFalsy(); |
| |
| BOOL success = NO; |
| NSError *error = nil; |
| id value = [signal asynchronousFirstOrDefault:nil success:&success error:&error]; |
| |
| expect(scheduledBlockRan).to.beTruthy(); |
| |
| expect(value).to.equal(RACUnit.defaultUnit); |
| expect(success).to.beTruthy(); |
| expect(error).to.beNil(); |
| }); |
| |
| it(@"should return a default value from -asynchronousFirstOrDefault:success:error:", ^{ |
| RACSignal *signal = [[RACSignal error:RACSignalTestError] delay:0]; |
| |
| __block BOOL scheduledBlockRan = NO; |
| [RACScheduler.mainThreadScheduler schedule:^{ |
| scheduledBlockRan = YES; |
| }]; |
| |
| expect(scheduledBlockRan).to.beFalsy(); |
| |
| BOOL success = NO; |
| NSError *error = nil; |
| id value = [signal asynchronousFirstOrDefault:RACUnit.defaultUnit success:&success error:&error]; |
| |
| expect(scheduledBlockRan).to.beTruthy(); |
| |
| expect(value).to.equal(RACUnit.defaultUnit); |
| expect(success).to.beFalsy(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should return a delayed error from -asynchronousFirstOrDefault:success:error:", ^{ |
| RACSignal *signal = [[RACSignal |
| createSignal:^(id<RACSubscriber> subscriber) { |
| return [[RACScheduler scheduler] schedule:^{ |
| [subscriber sendError:RACSignalTestError]; |
| }]; |
| }] |
| deliverOn:RACScheduler.mainThreadScheduler]; |
| |
| __block NSError *error = nil; |
| __block BOOL success = NO; |
| expect([signal asynchronousFirstOrDefault:nil success:&success error:&error]).to.beNil(); |
| |
| expect(success).to.beFalsy(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should terminate the subscription after returning from -asynchronousFirstOrDefault:success:error:", ^{ |
| __block BOOL disposed = NO; |
| RACSignal *signal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| [[RACScheduler scheduler] schedule:^{ |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| }]; |
| |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| |
| expect(signal).notTo.beNil(); |
| expect(disposed).to.beFalsy(); |
| |
| expect([signal asynchronousFirstOrDefault:nil success:NULL error:NULL]).to.equal(RACUnit.defaultUnit); |
| expect(disposed).will.beTruthy(); |
| }); |
| |
| it(@"should return a delayed success from -asynchronouslyWaitUntilCompleted:", ^{ |
| RACSignal *signal = [[RACSignal return:RACUnit.defaultUnit] delay:0]; |
| |
| __block BOOL scheduledBlockRan = NO; |
| [RACScheduler.mainThreadScheduler schedule:^{ |
| scheduledBlockRan = YES; |
| }]; |
| |
| expect(scheduledBlockRan).to.beFalsy(); |
| |
| NSError *error = nil; |
| BOOL success = [signal asynchronouslyWaitUntilCompleted:&error]; |
| |
| expect(scheduledBlockRan).to.beTruthy(); |
| |
| expect(success).to.beTruthy(); |
| expect(error).to.beNil(); |
| }); |
| }); |
| |
| describe(@"continuation", ^{ |
| it(@"should repeat after completion", ^{ |
| __block NSUInteger numberOfSubscriptions = 0; |
| RACScheduler *scheduler = [RACScheduler scheduler]; |
| |
| RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| return [scheduler schedule:^{ |
| if (numberOfSubscriptions == 3) { |
| [subscriber sendError:RACSignalTestError]; |
| return; |
| } |
| |
| numberOfSubscriptions++; |
| |
| [subscriber sendNext:@"1"]; |
| [subscriber sendCompleted]; |
| [subscriber sendError:RACSignalTestError]; |
| }]; |
| }]; |
| |
| __block NSUInteger nextCount = 0; |
| __block BOOL gotCompleted = NO; |
| [[signal repeat] subscribeNext:^(id x) { |
| nextCount++; |
| } error:^(NSError *error) { |
| |
| } completed:^{ |
| gotCompleted = YES; |
| }]; |
| |
| expect(nextCount).will.equal(3); |
| expect(gotCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should stop repeating when disposed", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| NSMutableArray *values = [NSMutableArray array]; |
| |
| __block BOOL completed = NO; |
| __block RACDisposable *disposable = [[signal repeat] subscribeNext:^(id x) { |
| [values addObject:x]; |
| [disposable dispose]; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| expect(values).will.equal(@[ @1 ]); |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should stop repeating when disposed by -take:", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| NSMutableArray *values = [NSMutableArray array]; |
| |
| __block BOOL completed = NO; |
| [[[signal repeat] take:1] subscribeNext:^(id x) { |
| [values addObject:x]; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| expect(values).will.equal(@[ @1 ]); |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"+combineLatestWith:", ^{ |
| __block RACSubject *subject1 = nil; |
| __block RACSubject *subject2 = nil; |
| __block RACSignal *combined = nil; |
| |
| beforeEach(^{ |
| subject1 = [RACSubject subject]; |
| subject2 = [RACSubject subject]; |
| combined = [RACSignal combineLatest:@[ subject1, subject2 ]]; |
| }); |
| |
| it(@"should send next only once both signals send next", ^{ |
| __block RACTuple *tuple; |
| |
| [combined subscribeNext:^(id x) { |
| tuple = x; |
| }]; |
| |
| expect(tuple).to.beNil(); |
| |
| [subject1 sendNext:@"1"]; |
| expect(tuple).to.beNil(); |
| |
| [subject2 sendNext:@"2"]; |
| expect(tuple).to.equal(RACTuplePack(@"1", @"2")); |
| }); |
| |
| it(@"should send nexts when either signal sends multiple times", ^{ |
| NSMutableArray *results = [NSMutableArray array]; |
| [combined subscribeNext:^(id x) { |
| [results addObject:x]; |
| }]; |
| |
| [subject1 sendNext:@"1"]; |
| [subject2 sendNext:@"2"]; |
| |
| [subject1 sendNext:@"3"]; |
| [subject2 sendNext:@"4"]; |
| |
| expect(results[0]).to.equal(RACTuplePack(@"1", @"2")); |
| expect(results[1]).to.equal(RACTuplePack(@"3", @"2")); |
| expect(results[2]).to.equal(RACTuplePack(@"3", @"4")); |
| }); |
| |
| it(@"should complete when only both signals complete", ^{ |
| __block BOOL completed = NO; |
| |
| [combined subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject1 sendCompleted]; |
| expect(completed).to.beFalsy(); |
| |
| [subject2 sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should error when either signal errors", ^{ |
| __block NSError *receivedError = nil; |
| [combined subscribeError:^(NSError *error) { |
| receivedError = error; |
| }]; |
| |
| [subject1 sendError:RACSignalTestError]; |
| expect(receivedError).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"shouldn't create a retain cycle", ^{ |
| __block BOOL subjectDeallocd = NO; |
| __block BOOL signalDeallocd = NO; |
| |
| @autoreleasepool { |
| RACSubject *subject __attribute__((objc_precise_lifetime)) = [RACSubject subject]; |
| [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| subjectDeallocd = YES; |
| }]]; |
| |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal combineLatest:@[ subject ]]; |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| signalDeallocd = YES; |
| }]]; |
| |
| [signal subscribeCompleted:^{}]; |
| [subject sendCompleted]; |
| } |
| |
| expect(subjectDeallocd).will.beTruthy(); |
| expect(signalDeallocd).will.beTruthy(); |
| }); |
| |
| it(@"should combine the same signal", ^{ |
| RACSignal *combined = [subject1 combineLatestWith:subject1]; |
| |
| __block RACTuple *tuple; |
| [combined subscribeNext:^(id x) { |
| tuple = x; |
| }]; |
| |
| [subject1 sendNext:@"foo"]; |
| expect(tuple).to.equal(RACTuplePack(@"foo", @"foo")); |
| |
| [subject1 sendNext:@"bar"]; |
| expect(tuple).to.equal(RACTuplePack(@"bar", @"bar")); |
| }); |
| |
| it(@"should combine the same side-effecting signal", ^{ |
| __block NSUInteger counter = 0; |
| RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@(++counter)]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| RACSignal *combined = [sideEffectingSignal combineLatestWith:sideEffectingSignal]; |
| expect(counter).to.equal(0); |
| |
| NSMutableArray *receivedValues = [NSMutableArray array]; |
| [combined subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| expect(counter).to.equal(2); |
| |
| NSArray *expected = @[ RACTuplePack(@1, @2) ]; |
| expect(receivedValues).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"+combineLatest:", ^{ |
| it(@"should return tuples even when only combining one signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| __block RACTuple *tuple; |
| [[RACSignal combineLatest:@[ subject ]] subscribeNext:^(id x) { |
| tuple = x; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| expect(tuple).to.equal(RACTuplePack(@"foo")); |
| }); |
| |
| it(@"should complete immediately when not given any signals", ^{ |
| RACSignal *signal = [RACSignal combineLatest:@[]]; |
| |
| __block BOOL completed = NO; |
| [signal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should only complete after all its signals complete", ^{ |
| RACSubject *subject1 = [RACSubject subject]; |
| RACSubject *subject2 = [RACSubject subject]; |
| RACSubject *subject3 = [RACSubject subject]; |
| RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ]]; |
| |
| __block BOOL completed = NO; |
| [combined subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject1 sendCompleted]; |
| expect(completed).to.beFalsy(); |
| |
| [subject2 sendCompleted]; |
| expect(completed).to.beFalsy(); |
| |
| [subject3 sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"+combineLatest:reduce:", ^{ |
| __block RACSubject *subject1; |
| __block RACSubject *subject2; |
| __block RACSubject *subject3; |
| |
| beforeEach(^{ |
| subject1 = [RACSubject subject]; |
| subject2 = [RACSubject subject]; |
| subject3 = [RACSubject subject]; |
| }); |
| |
| it(@"should send nils for nil values", ^{ |
| __block id receivedVal1; |
| __block id receivedVal2; |
| __block id receivedVal3; |
| |
| RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ] reduce:^ id (id val1, id val2, id val3) { |
| receivedVal1 = val1; |
| receivedVal2 = val2; |
| receivedVal3 = val3; |
| return nil; |
| }]; |
| |
| __block BOOL gotValue = NO; |
| [combined subscribeNext:^(id x) { |
| gotValue = YES; |
| }]; |
| |
| [subject1 sendNext:nil]; |
| [subject2 sendNext:nil]; |
| [subject3 sendNext:nil]; |
| |
| expect(gotValue).to.beTruthy(); |
| expect(receivedVal1).to.beNil(); |
| expect(receivedVal2).to.beNil(); |
| expect(receivedVal3).to.beNil(); |
| }); |
| |
| it(@"should send the return result of the reduce block", ^{ |
| RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject3 ] reduce:^(NSString *string1, NSString *string2, NSString *string3) { |
| return [NSString stringWithFormat:@"%@: %@%@", string1, string2, string3]; |
| }]; |
| |
| __block id received; |
| [combined subscribeNext:^(id x) { |
| received = x; |
| }]; |
| |
| [subject1 sendNext:@"hello"]; |
| [subject2 sendNext:@"world"]; |
| [subject3 sendNext:@"!!1"]; |
| |
| expect(received).to.equal(@"hello: world!!1"); |
| }); |
| |
| it(@"should handle multiples of the same signals", ^{ |
| RACSignal *combined = [RACSignal combineLatest:@[ subject1, subject2, subject1, subject3 ] reduce:^(NSString *string1, NSString *string2, NSString *string3, NSString *string4) { |
| return [NSString stringWithFormat:@"%@ : %@ = %@ : %@", string1, string2, string3, string4]; |
| }]; |
| |
| NSMutableArray *receivedValues = NSMutableArray.array; |
| |
| [combined subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| [subject1 sendNext:@"apples"]; |
| expect(receivedValues.lastObject).to.beNil(); |
| |
| [subject2 sendNext:@"oranges"]; |
| expect(receivedValues.lastObject).to.beNil(); |
| |
| [subject3 sendNext:@"cattle"]; |
| expect(receivedValues.lastObject).to.equal(@"apples : oranges = apples : cattle"); |
| |
| [subject1 sendNext:@"horses"]; |
| expect(receivedValues.lastObject).to.equal(@"horses : oranges = horses : cattle"); |
| }); |
| |
| it(@"should handle multiples of the same side-effecting signal", ^{ |
| __block NSUInteger counter = 0; |
| RACSignal *sideEffectingSignal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@(++counter)]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| RACSignal *combined = [RACSignal combineLatest:@[ sideEffectingSignal, sideEffectingSignal, sideEffectingSignal ] reduce:^(id x, id y, id z) { |
| return [NSString stringWithFormat:@"%@%@%@", x, y, z]; |
| }]; |
| |
| NSMutableArray *receivedValues = [NSMutableArray array]; |
| expect(counter).to.equal(0); |
| |
| [combined subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| expect(counter).to.equal(3); |
| expect(receivedValues).to.equal(@[ @"123" ]); |
| }); |
| }); |
| |
| describe(@"distinctUntilChanged", ^{ |
| it(@"should only send values that are distinct from the previous value", ^{ |
| RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| [subscriber sendNext:@2]; |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }] distinctUntilChanged]; |
| |
| NSArray *values = sub.toArray; |
| NSArray *expected = @[ @1, @2, @1 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"shouldn't consider nils to always be distinct", ^{ |
| RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }] distinctUntilChanged]; |
| |
| NSArray *values = sub.toArray; |
| NSArray *expected = @[ @1, [NSNull null], @1 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should consider initial nil to be distinct", ^{ |
| RACSignal *sub = [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }] distinctUntilChanged]; |
| |
| NSArray *values = sub.toArray; |
| NSArray *expected = @[ [NSNull null], @1 ]; |
| expect(values).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"RACObserve", ^{ |
| __block RACTestObject *testObject; |
| |
| beforeEach(^{ |
| testObject = [[RACTestObject alloc] init]; |
| }); |
| |
| it(@"should work with object properties", ^{ |
| NSArray *expected = @[ @"hello", @"world" ]; |
| testObject.objectValue = expected[0]; |
| |
| NSMutableArray *valuesReceived = [NSMutableArray array]; |
| [RACObserve(testObject, objectValue) subscribeNext:^(id x) { |
| [valuesReceived addObject:x]; |
| }]; |
| |
| testObject.objectValue = expected[1]; |
| |
| expect(valuesReceived).to.equal(expected); |
| }); |
| |
| it(@"should work with non-object properties", ^{ |
| NSArray *expected = @[ @42, @43 ]; |
| testObject.integerValue = [expected[0] integerValue]; |
| |
| NSMutableArray *valuesReceived = [NSMutableArray array]; |
| [RACObserve(testObject, integerValue) subscribeNext:^(id x) { |
| [valuesReceived addObject:x]; |
| }]; |
| |
| testObject.integerValue = [expected[1] integerValue]; |
| |
| expect(valuesReceived).to.equal(expected); |
| }); |
| |
| it(@"should read the initial value upon subscription", ^{ |
| testObject.objectValue = @"foo"; |
| |
| RACSignal *signal = RACObserve(testObject, objectValue); |
| testObject.objectValue = @"bar"; |
| |
| expect([signal first]).to.equal(@"bar"); |
| }); |
| }); |
| |
| describe(@"-setKeyPath:onObject:", ^{ |
| id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) { |
| [signal setKeyPath:keyPath onObject:testObject nilValue:nilValue]; |
| }; |
| |
| itShouldBehaveLike(RACPropertySignalExamples, ^{ |
| return @{ RACPropertySignalExamplesSetupBlock: setupBlock }; |
| }); |
| |
| it(@"shouldn't send values to dealloc'd objects", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| @autoreleasepool { |
| RACTestObject *testObject __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init]; |
| [subject setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; |
| expect(testObject.objectValue).to.beNil(); |
| |
| [subject sendNext:@1]; |
| expect(testObject.objectValue).to.equal(@1); |
| |
| [subject sendNext:@2]; |
| expect(testObject.objectValue).to.equal(@2); |
| } |
| |
| // This shouldn't do anything. |
| [subject sendNext:@3]; |
| }); |
| |
| it(@"should allow a new derivation after the signal's completed", ^{ |
| RACSubject *subject1 = [RACSubject subject]; |
| RACTestObject *testObject = [[RACTestObject alloc] init]; |
| [subject1 setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; |
| [subject1 sendCompleted]; |
| |
| RACSubject *subject2 = [RACSubject subject]; |
| // This will assert if the previous completion didn't dispose of the |
| // subscription. |
| [subject2 setKeyPath:@keypath(testObject.objectValue) onObject:testObject]; |
| }); |
| |
| it(@"should set the given value when nil is received", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACTestObject *testObject = [[RACTestObject alloc] init]; |
| [subject setKeyPath:@keypath(testObject.integerValue) onObject:testObject nilValue:@5]; |
| |
| [subject sendNext:@1]; |
| expect(testObject.integerValue).to.equal(1); |
| |
| [subject sendNext:nil]; |
| expect(testObject.integerValue).to.equal(5); |
| |
| [subject sendCompleted]; |
| expect(testObject.integerValue).to.equal(5); |
| }); |
| |
| it(@"should keep object alive over -sendNext:", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| __block RACTestObject *testObject = [[RACTestObject alloc] init]; |
| __block id deallocValue; |
| |
| __unsafe_unretained RACTestObject *unsafeTestObject = testObject; |
| [testObject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocValue = unsafeTestObject.slowObjectValue; |
| }]]; |
| |
| [subject setKeyPath:@keypath(testObject.slowObjectValue) onObject:testObject]; |
| expect(testObject.slowObjectValue).to.beNil(); |
| |
| // Attempt to deallocate concurrently. |
| [[RACScheduler scheduler] afterDelay:0.01 schedule:^{ |
| testObject = nil; |
| }]; |
| |
| expect(deallocValue).to.beNil(); |
| [subject sendNext:@1]; |
| expect(deallocValue).to.equal(@1); |
| }); |
| }); |
| |
| describe(@"memory management", ^{ |
| it(@"should dealloc signals if the signal does nothing", ^{ |
| __block BOOL deallocd = NO; |
| @autoreleasepool { |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| } |
| |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should retain signals for a single run loop iteration", ^{ |
| __block BOOL deallocd = NO; |
| |
| @autoreleasepool { |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| } |
| |
| expect(deallocd).to.beFalsy(); |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should dealloc signals if the signal immediately completes", ^{ |
| __block BOOL deallocd = NO; |
| @autoreleasepool { |
| __block BOOL done = NO; |
| |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| |
| [signal subscribeCompleted:^{ |
| done = YES; |
| }]; |
| |
| expect(done).will.beTruthy(); |
| } |
| |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should dealloc a replay subject if it completes immediately", ^{ |
| __block BOOL completed = NO; |
| __block BOOL deallocd = NO; |
| @autoreleasepool { |
| RACReplaySubject *subject __attribute__((objc_precise_lifetime)) = [RACReplaySubject subject]; |
| [subject sendCompleted]; |
| |
| [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| |
| [subject subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| } |
| |
| expect(completed).will.beTruthy(); |
| |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should dealloc if the signal was created on a background queue", ^{ |
| __block BOOL completed = NO; |
| __block BOOL deallocd = NO; |
| @autoreleasepool { |
| [[RACScheduler scheduler] schedule:^{ |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| |
| [signal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| }]; |
| } |
| |
| expect(completed).will.beTruthy(); |
| |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should dealloc if the signal was created on a background queue, never gets any subscribers, and the background queue gets delayed", ^{ |
| __block BOOL deallocd = NO; |
| @autoreleasepool { |
| [[RACScheduler scheduler] schedule:^{ |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| |
| [NSThread sleepForTimeInterval:1]; |
| |
| expect(deallocd).to.beFalsy(); |
| }]; |
| } |
| |
| // The default test timeout is 1s so we'd race to see if the queue delay |
| // or default timeout happens first. To avoid that, just bump the |
| // timeout slightly for this test. |
| NSTimeInterval originalTestTimeout = Expecta.asynchronousTestTimeout; |
| Expecta.asynchronousTestTimeout = 1.1f; |
| expect(deallocd).will.beTruthy(); |
| Expecta.asynchronousTestTimeout = originalTestTimeout; |
| }); |
| |
| it(@"should retain signals when subscribing", ^{ |
| __block BOOL deallocd = NO; |
| |
| RACDisposable *disposable; |
| @autoreleasepool { |
| @autoreleasepool { |
| @autoreleasepool { |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| return nil; |
| }]; |
| |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| deallocd = YES; |
| }]]; |
| |
| disposable = [signal subscribeCompleted:^{}]; |
| } |
| |
| // Spin the run loop to account for RAC magic that retains the |
| // signal for a single iteration. |
| [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; |
| } |
| |
| expect(deallocd).to.beFalsy(); |
| |
| [disposable dispose]; |
| } |
| expect(deallocd).will.beTruthy(); |
| }); |
| |
| it(@"should retain intermediate signals when subscribing", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| expect(subject).notTo.beNil(); |
| |
| __block BOOL gotNext = NO; |
| __block BOOL completed = NO; |
| |
| RACDisposable *disposable; |
| |
| @autoreleasepool { |
| @autoreleasepool { |
| RACSignal *intermediateSignal = [subject doNext:^(id _) { |
| gotNext = YES; |
| }]; |
| |
| expect(intermediateSignal).notTo.beNil(); |
| |
| disposable = [intermediateSignal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| } |
| |
| // Spin the run loop to account for RAC magic that retains the |
| // signal for a single iteration. |
| [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]]; |
| } |
| |
| [subject sendNext:@5]; |
| expect(gotNext).to.beTruthy(); |
| |
| [subject sendCompleted]; |
| expect(completed).to.beTruthy(); |
| |
| [disposable dispose]; |
| }); |
| }); |
| |
| describe(@"-merge:", ^{ |
| __block RACSubject *sub1; |
| __block RACSubject *sub2; |
| __block RACSignal *merged; |
| beforeEach(^{ |
| sub1 = [RACSubject subject]; |
| sub2 = [RACSubject subject]; |
| merged = [sub1 merge:sub2]; |
| }); |
| |
| it(@"should send all values from both signals", ^{ |
| NSMutableArray *values = [NSMutableArray array]; |
| [merged subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| [sub1 sendNext:@1]; |
| [sub2 sendNext:@2]; |
| [sub2 sendNext:@3]; |
| [sub1 sendNext:@4]; |
| |
| NSArray *expected = @[ @1, @2, @3, @4 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should send an error if one occurs", ^{ |
| __block NSError *errorReceived; |
| [merged subscribeError:^(NSError *error) { |
| errorReceived = error; |
| }]; |
| |
| [sub1 sendError:RACSignalTestError]; |
| expect(errorReceived).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should complete only after both signals complete", ^{ |
| NSMutableArray *values = [NSMutableArray array]; |
| __block BOOL completed = NO; |
| [merged subscribeNext:^(id x) { |
| [values addObject:x]; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| [sub1 sendNext:@1]; |
| [sub2 sendNext:@2]; |
| [sub2 sendNext:@3]; |
| [sub2 sendCompleted]; |
| expect(completed).to.beFalsy(); |
| |
| [sub1 sendNext:@4]; |
| [sub1 sendCompleted]; |
| expect(completed).to.beTruthy(); |
| |
| NSArray *expected = @[ @1, @2, @3, @4 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should complete only after both signals complete for any number of subscribers", ^{ |
| __block BOOL completed1 = NO; |
| __block BOOL completed2 = NO; |
| [merged subscribeCompleted:^{ |
| completed1 = YES; |
| }]; |
| |
| [merged subscribeCompleted:^{ |
| completed2 = YES; |
| }]; |
| |
| expect(completed1).to.beFalsy(); |
| expect(completed2).to.beFalsy(); |
| |
| [sub1 sendCompleted]; |
| [sub2 sendCompleted]; |
| expect(completed1).to.beTruthy(); |
| expect(completed2).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"+merge:", ^{ |
| __block RACSubject *sub1; |
| __block RACSubject *sub2; |
| __block RACSignal *merged; |
| beforeEach(^{ |
| sub1 = [RACSubject subject]; |
| sub2 = [RACSubject subject]; |
| merged = [RACSignal merge:@[ sub1, sub2 ].objectEnumerator]; |
| }); |
| |
| it(@"should send all values from both signals", ^{ |
| NSMutableArray *values = [NSMutableArray array]; |
| [merged subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| [sub1 sendNext:@1]; |
| [sub2 sendNext:@2]; |
| [sub2 sendNext:@3]; |
| [sub1 sendNext:@4]; |
| |
| NSArray *expected = @[ @1, @2, @3, @4 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should send an error if one occurs", ^{ |
| __block NSError *errorReceived; |
| [merged subscribeError:^(NSError *error) { |
| errorReceived = error; |
| }]; |
| |
| [sub1 sendError:RACSignalTestError]; |
| expect(errorReceived).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should complete only after both signals complete", ^{ |
| NSMutableArray *values = [NSMutableArray array]; |
| __block BOOL completed = NO; |
| [merged subscribeNext:^(id x) { |
| [values addObject:x]; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| [sub1 sendNext:@1]; |
| [sub2 sendNext:@2]; |
| [sub2 sendNext:@3]; |
| [sub2 sendCompleted]; |
| expect(completed).to.beFalsy(); |
| |
| [sub1 sendNext:@4]; |
| [sub1 sendCompleted]; |
| expect(completed).to.beTruthy(); |
| |
| NSArray *expected = @[ @1, @2, @3, @4 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should complete immediately when not given any signals", ^{ |
| RACSignal *signal = [RACSignal merge:@[].objectEnumerator]; |
| |
| __block BOOL completed = NO; |
| [signal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should complete only after both signals complete for any number of subscribers", ^{ |
| __block BOOL completed1 = NO; |
| __block BOOL completed2 = NO; |
| [merged subscribeCompleted:^{ |
| completed1 = YES; |
| }]; |
| |
| [merged subscribeCompleted:^{ |
| completed2 = YES; |
| }]; |
| |
| expect(completed1).to.beFalsy(); |
| expect(completed2).to.beFalsy(); |
| |
| [sub1 sendCompleted]; |
| [sub2 sendCompleted]; |
| expect(completed1).to.beTruthy(); |
| expect(completed2).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-flatten:", ^{ |
| __block BOOL subscribedTo1 = NO; |
| __block BOOL subscribedTo2 = NO; |
| __block BOOL subscribedTo3 = NO; |
| __block RACSignal *sub1; |
| __block RACSignal *sub2; |
| __block RACSignal *sub3; |
| __block RACSubject *subject1; |
| __block RACSubject *subject2; |
| __block RACSubject *subject3; |
| __block RACSubject *signalsSubject; |
| __block NSMutableArray *values; |
| |
| beforeEach(^{ |
| subscribedTo1 = NO; |
| subject1 = [RACSubject subject]; |
| sub1 = [RACSignal defer:^{ |
| subscribedTo1 = YES; |
| return subject1; |
| }]; |
| |
| subscribedTo2 = NO; |
| subject2 = [RACSubject subject]; |
| sub2 = [RACSignal defer:^{ |
| subscribedTo2 = YES; |
| return subject2; |
| }]; |
| |
| subscribedTo3 = NO; |
| subject3 = [RACSubject subject]; |
| sub3 = [RACSignal defer:^{ |
| subscribedTo3 = YES; |
| return subject3; |
| }]; |
| |
| signalsSubject = [RACSubject subject]; |
| |
| values = [NSMutableArray array]; |
| }); |
| |
| describe(@"when its max is 0", ^{ |
| it(@"should merge all the signals concurrently", ^{ |
| [[signalsSubject flatten:0] subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| expect(subscribedTo1).to.beFalsy(); |
| expect(subscribedTo2).to.beFalsy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [signalsSubject sendNext:sub1]; |
| [signalsSubject sendNext:sub2]; |
| |
| expect(subscribedTo1).to.beTruthy(); |
| expect(subscribedTo2).to.beTruthy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [subject1 sendNext:@1]; |
| |
| [signalsSubject sendNext:sub3]; |
| |
| expect(subscribedTo1).to.beTruthy(); |
| expect(subscribedTo2).to.beTruthy(); |
| expect(subscribedTo3).to.beTruthy(); |
| |
| [subject1 sendCompleted]; |
| |
| [subject2 sendNext:@2]; |
| [subject2 sendCompleted]; |
| |
| [subject3 sendNext:@3]; |
| [subject3 sendCompleted]; |
| |
| NSArray *expected = @[ @1, @2, @3 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| itShouldBehaveLike(RACSignalMergeConcurrentCompletionExampleGroup, @{ RACSignalMaxConcurrent: @0 }); |
| }); |
| |
| describe(@"when its max is > 0", ^{ |
| it(@"should merge only the given number at a time", ^{ |
| [[signalsSubject flatten:1] subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| expect(subscribedTo1).to.beFalsy(); |
| expect(subscribedTo2).to.beFalsy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [signalsSubject sendNext:sub1]; |
| [signalsSubject sendNext:sub2]; |
| |
| expect(subscribedTo1).to.beTruthy(); |
| expect(subscribedTo2).to.beFalsy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [subject1 sendNext:@1]; |
| |
| [signalsSubject sendNext:sub3]; |
| |
| expect(subscribedTo1).to.beTruthy(); |
| expect(subscribedTo2).to.beFalsy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [signalsSubject sendCompleted]; |
| |
| expect(subscribedTo1).to.beTruthy(); |
| expect(subscribedTo2).to.beFalsy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [subject1 sendCompleted]; |
| |
| expect(subscribedTo2).to.beTruthy(); |
| expect(subscribedTo3).to.beFalsy(); |
| |
| [subject2 sendNext:@2]; |
| [subject2 sendCompleted]; |
| |
| expect(subscribedTo3).to.beTruthy(); |
| |
| [subject3 sendNext:@3]; |
| [subject3 sendCompleted]; |
| |
| NSArray *expected = @[ @1, @2, @3 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| itShouldBehaveLike(RACSignalMergeConcurrentCompletionExampleGroup, @{ RACSignalMaxConcurrent: @1 }); |
| }); |
| |
| it(@"shouldn't create a retain cycle", ^{ |
| __block BOOL subjectDeallocd = NO; |
| __block BOOL signalDeallocd = NO; |
| @autoreleasepool { |
| RACSubject *subject __attribute__((objc_precise_lifetime)) = [RACSubject subject]; |
| [subject.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| subjectDeallocd = YES; |
| }]]; |
| |
| RACSignal *signal __attribute__((objc_precise_lifetime)) = [subject flatten]; |
| [signal.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{ |
| signalDeallocd = YES; |
| }]]; |
| |
| [signal subscribeCompleted:^{}]; |
| |
| [subject sendCompleted]; |
| } |
| |
| expect(subjectDeallocd).will.beTruthy(); |
| expect(signalDeallocd).will.beTruthy(); |
| }); |
| |
| it(@"should not crash when disposing while subscribing", ^{ |
| RACDisposable *disposable = [[signalsSubject flatten:0] subscribeCompleted:^{ |
| }]; |
| |
| [signalsSubject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [disposable dispose]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]]; |
| |
| [signalsSubject sendCompleted]; |
| }); |
| |
| it(@"should dispose after last synchronous signal subscription and should not crash", ^{ |
| |
| RACSignal *flattened = [signalsSubject flatten:1]; |
| RACDisposable *flattenDisposable = [flattened subscribeCompleted:^{}]; |
| |
| RACSignal *syncSignal = [RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber) { |
| expect(flattenDisposable.disposed).to.beFalsy(); |
| [subscriber sendCompleted]; |
| expect(flattenDisposable.disposed).to.beTruthy(); |
| return nil; |
| }]; |
| |
| RACSignal *asyncSignal = [sub1 delay:0]; |
| |
| [signalsSubject sendNext:asyncSignal]; |
| [signalsSubject sendNext:syncSignal]; |
| |
| [signalsSubject sendCompleted]; |
| |
| [subject1 sendCompleted]; |
| |
| expect(flattenDisposable.disposed).will.beTruthy(); |
| }); |
| |
| it(@"should not crash when disposed because of takeUntil:", ^{ |
| for (int i = 0; i < 100; i++) { |
| RACSubject *flattenedReceiver = [RACSubject subject]; |
| RACSignal *done = [flattenedReceiver map:^(NSNumber *n) { |
| return @(n.integerValue == 1); |
| }]; |
| |
| RACSignal *flattened = [signalsSubject flatten:1]; |
| |
| RACDisposable *flattenDisposable = [[flattened takeUntil:[done ignore:@NO]] subscribe:flattenedReceiver]; |
| |
| RACSignal *syncSignal = [RACSignal createSignal:^ RACDisposable *(id<RACSubscriber> subscriber) { |
| expect(flattenDisposable.disposed).to.beFalsy(); |
| [subscriber sendNext:@1]; |
| expect(flattenDisposable.disposed).to.beTruthy(); |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| RACSignal *asyncSignal = [sub1 delay:0]; |
| [subject1 sendNext:@0]; |
| |
| [signalsSubject sendNext:asyncSignal]; |
| [signalsSubject sendNext:syncSignal]; |
| [signalsSubject sendCompleted]; |
| |
| [subject1 sendCompleted]; |
| |
| expect(flattenDisposable.disposed).will.beTruthy(); |
| } |
| }); |
| }); |
| |
| describe(@"-switchToLatest", ^{ |
| __block RACSubject *subject; |
| |
| __block NSMutableArray *values; |
| __block NSError *lastError = nil; |
| __block BOOL completed = NO; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| |
| values = [NSMutableArray array]; |
| lastError = nil; |
| completed = NO; |
| |
| [[subject switchToLatest] subscribeNext:^(id x) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [values addObject:x]; |
| } error:^(NSError *error) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| lastError = error; |
| } completed:^{ |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| completed = YES; |
| }]; |
| }); |
| |
| it(@"should send values from the most recent signal", ^{ |
| [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| return nil; |
| }]]; |
| |
| [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@3]; |
| [subscriber sendNext:@4]; |
| return nil; |
| }]]; |
| |
| NSArray *expected = @[ @1, @2, @3, @4 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should send errors from the most recent signal", ^{ |
| [subject sendNext:[RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| return nil; |
| }]]; |
| |
| expect(lastError).notTo.beNil(); |
| }); |
| |
| it(@"should not send completed if only the switching signal completes", ^{ |
| [subject sendNext:RACSignal.never]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject sendCompleted]; |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should send completed when the switching signal completes and the last sent signal does", ^{ |
| [subject sendNext:RACSignal.empty]; |
| |
| expect(completed).to.beFalsy(); |
| |
| [subject sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should accept nil signals", ^{ |
| [subject sendNext:nil]; |
| [subject sendNext:[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| return nil; |
| }]]; |
| |
| NSArray *expected = @[ @1, @2 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should return a cold signal", ^{ |
| __block NSUInteger subscriptions = 0; |
| RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| subscriptions++; |
| [subscriber sendNext:[RACSignal empty]]; |
| return nil; |
| }]; |
| |
| RACSignal *switched = [signalOfSignals switchToLatest]; |
| |
| [[switched publish] connect]; |
| expect(subscriptions).to.equal(1); |
| |
| [[switched publish] connect]; |
| expect(subscriptions).to.equal(2); |
| }); |
| }); |
| |
| describe(@"+switch:cases:default:", ^{ |
| __block RACSubject *keySubject; |
| |
| __block RACSubject *subjectZero; |
| __block RACSubject *subjectOne; |
| __block RACSubject *subjectTwo; |
| |
| __block RACSubject *defaultSubject; |
| |
| __block NSMutableArray *values; |
| __block NSError *lastError = nil; |
| __block BOOL completed = NO; |
| |
| beforeEach(^{ |
| keySubject = [RACSubject subject]; |
| |
| subjectZero = [RACSubject subject]; |
| subjectOne = [RACSubject subject]; |
| subjectTwo = [RACSubject subject]; |
| |
| defaultSubject = [RACSubject subject]; |
| |
| values = [NSMutableArray array]; |
| lastError = nil; |
| completed = NO; |
| }); |
| |
| describe(@"switching between values with a default", ^{ |
| __block RACSignal *switchSignal; |
| |
| beforeEach(^{ |
| switchSignal = [RACSignal switch:keySubject cases:@{ |
| @0: subjectZero, |
| @1: subjectOne, |
| @2: subjectTwo, |
| } default:[RACSignal never]]; |
| |
| [switchSignal subscribeNext:^(id x) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [values addObject:x]; |
| } error:^(NSError *error) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| lastError = error; |
| } completed:^{ |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| completed = YES; |
| }]; |
| }); |
| |
| it(@"should not send any values before a key is sent", ^{ |
| [subjectZero sendNext:RACUnit.defaultUnit]; |
| [subjectOne sendNext:RACUnit.defaultUnit]; |
| [subjectTwo sendNext:RACUnit.defaultUnit]; |
| |
| expect(values).to.equal(@[]); |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should send events based on the latest key", ^{ |
| [keySubject sendNext:@0]; |
| |
| [subjectZero sendNext:@"zero"]; |
| [subjectZero sendNext:@"zero"]; |
| [subjectOne sendNext:@"one"]; |
| [subjectTwo sendNext:@"two"]; |
| |
| NSArray *expected = @[ @"zero", @"zero" ]; |
| expect(values).to.equal(expected); |
| |
| [keySubject sendNext:@1]; |
| |
| [subjectZero sendNext:@"zero"]; |
| [subjectOne sendNext:@"one"]; |
| [subjectTwo sendNext:@"two"]; |
| |
| expected = @[ @"zero", @"zero", @"one" ]; |
| expect(values).to.equal(expected); |
| |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [keySubject sendNext:@2]; |
| |
| [subjectZero sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| [subjectOne sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| expect(lastError).to.beNil(); |
| |
| [subjectTwo sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| expect(lastError).notTo.beNil(); |
| }); |
| |
| it(@"should not send completed when only the key signal completes", ^{ |
| [keySubject sendNext:@0]; |
| [subjectZero sendNext:@"zero"]; |
| [keySubject sendCompleted]; |
| |
| expect(values).to.equal(@[ @"zero" ]); |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should send completed when the key signal and the latest sent signal complete", ^{ |
| [keySubject sendNext:@0]; |
| [subjectZero sendNext:@"zero"]; |
| [keySubject sendCompleted]; |
| [subjectZero sendCompleted]; |
| |
| expect(values).to.equal(@[ @"zero" ]); |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| it(@"should use the default signal if key that was sent does not have an associated signal", ^{ |
| [[RACSignal |
| switch:keySubject |
| cases:@{ |
| @0: subjectZero, |
| @1: subjectOne, |
| } |
| default:defaultSubject] |
| subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| [keySubject sendNext:@"not a valid key"]; |
| [defaultSubject sendNext:@"default"]; |
| |
| expect(values).to.equal(@[ @"default" ]); |
| |
| [keySubject sendNext:nil]; |
| [defaultSubject sendNext:@"default"]; |
| |
| expect(values).to.equal((@[ @"default", @"default" ])); |
| }); |
| |
| it(@"should send an error if key that was sent does not have an associated signal and there's no default", ^{ |
| [[RACSignal |
| switch:keySubject |
| cases:@{ |
| @0: subjectZero, |
| @1: subjectOne, |
| } |
| default:nil] |
| subscribeNext:^(id x) { |
| [values addObject:x]; |
| } error:^(NSError *error) { |
| lastError = error; |
| }]; |
| |
| [keySubject sendNext:@0]; |
| [subjectZero sendNext:@"zero"]; |
| |
| expect(values).to.equal(@[ @"zero" ]); |
| expect(lastError).to.beNil(); |
| |
| [keySubject sendNext:nil]; |
| |
| expect(values).to.equal(@[ @"zero" ]); |
| expect(lastError).notTo.beNil(); |
| expect(lastError.domain).to.equal(RACSignalErrorDomain); |
| expect(lastError.code).to.equal(RACSignalErrorNoMatchingCase); |
| }); |
| |
| it(@"should match RACTupleNil case when a nil value is sent", ^{ |
| [[RACSignal |
| switch:keySubject |
| cases:@{ |
| RACTupleNil.tupleNil: subjectZero, |
| } |
| default:defaultSubject] |
| subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| [keySubject sendNext:nil]; |
| [subjectZero sendNext:@"zero"]; |
| expect(values).to.equal(@[ @"zero" ]); |
| }); |
| }); |
| |
| describe(@"+if:then:else", ^{ |
| __block RACSubject *boolSubject; |
| __block RACSubject *trueSubject; |
| __block RACSubject *falseSubject; |
| |
| __block NSMutableArray *values; |
| __block NSError *lastError = nil; |
| __block BOOL completed = NO; |
| |
| beforeEach(^{ |
| boolSubject = [RACSubject subject]; |
| trueSubject = [RACSubject subject]; |
| falseSubject = [RACSubject subject]; |
| |
| values = [NSMutableArray array]; |
| lastError = nil; |
| completed = NO; |
| |
| [[RACSignal if:boolSubject then:trueSubject else:falseSubject] subscribeNext:^(id x) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [values addObject:x]; |
| } error:^(NSError *error) { |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| lastError = error; |
| } completed:^{ |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| completed = YES; |
| }]; |
| }); |
| |
| it(@"should not send any values before a boolean is sent", ^{ |
| [trueSubject sendNext:RACUnit.defaultUnit]; |
| [falseSubject sendNext:RACUnit.defaultUnit]; |
| |
| expect(values).to.equal(@[]); |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should send events based on the latest boolean", ^{ |
| [boolSubject sendNext:@YES]; |
| |
| [trueSubject sendNext:@"foo"]; |
| [falseSubject sendNext:@"buzz"]; |
| [trueSubject sendNext:@"bar"]; |
| |
| NSArray *expected = @[ @"foo", @"bar" ]; |
| expect(values).to.equal(expected); |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [boolSubject sendNext:@NO]; |
| |
| [trueSubject sendNext:@"baz"]; |
| [falseSubject sendNext:@"buzz"]; |
| [trueSubject sendNext:@"barfoo"]; |
| |
| expected = @[ @"foo", @"bar", @"buzz" ]; |
| expect(values).to.equal(expected); |
| expect(lastError).to.beNil(); |
| expect(completed).to.beFalsy(); |
| |
| [trueSubject sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| expect(lastError).to.beNil(); |
| |
| [falseSubject sendError:[NSError errorWithDomain:@"" code:-1 userInfo:nil]]; |
| expect(lastError).notTo.beNil(); |
| }); |
| |
| it(@"should not send completed when only the BOOL signal completes", ^{ |
| [boolSubject sendNext:@YES]; |
| [trueSubject sendNext:@"foo"]; |
| [boolSubject sendCompleted]; |
| |
| expect(values).to.equal(@[ @"foo" ]); |
| expect(completed).to.beFalsy(); |
| }); |
| |
| it(@"should send completed when the BOOL signal and the latest sent signal complete", ^{ |
| [boolSubject sendNext:@YES]; |
| [trueSubject sendNext:@"foo"]; |
| [trueSubject sendCompleted]; |
| [boolSubject sendCompleted]; |
| |
| expect(values).to.equal(@[ @"foo" ]); |
| expect(completed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"+interval:onScheduler: and +interval:onScheduler:withLeeway:", ^{ |
| static const NSTimeInterval interval = 0.1; |
| static const NSTimeInterval leeway = 0.2; |
| |
| __block void (^testTimer)(RACSignal *, NSNumber *, NSNumber *) = nil; |
| |
| before(^{ |
| testTimer = [^(RACSignal *timer, NSNumber *minInterval, NSNumber *leeway) { |
| __block NSUInteger nextsReceived = 0; |
| |
| NSTimeInterval startTime = NSDate.timeIntervalSinceReferenceDate; |
| [[timer take:3] subscribeNext:^(NSDate *date) { |
| ++nextsReceived; |
| |
| NSTimeInterval currentTime = date.timeIntervalSinceReferenceDate; |
| |
| // Uniformly distribute the expected interval for all |
| // received values. We do this instead of saving a timestamp |
| // because a delayed interval may cause the _next_ value to |
| // send sooner than the interval. |
| NSTimeInterval expectedMinInterval = minInterval.doubleValue * nextsReceived; |
| NSTimeInterval expectedMaxInterval = expectedMinInterval + leeway.doubleValue * 3 + 0.05; |
| |
| expect(currentTime - startTime).beGreaterThanOrEqualTo(expectedMinInterval); |
| expect(currentTime - startTime).beLessThanOrEqualTo(expectedMaxInterval); |
| }]; |
| |
| expect(nextsReceived).will.equal(3); |
| } copy]; |
| }); |
| |
| describe(@"+interval:onScheduler:", ^{ |
| it(@"should work on the main thread scheduler", ^{ |
| testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler], @(interval), @0); |
| }); |
| |
| it(@"should work on a background scheduler", ^{ |
| testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler]], @(interval), @0); |
| }); |
| }); |
| |
| describe(@"+interval:onScheduler:withLeeway:", ^{ |
| it(@"should work on the main thread scheduler", ^{ |
| testTimer([RACSignal interval:interval onScheduler:RACScheduler.mainThreadScheduler withLeeway:leeway], @(interval), @(leeway)); |
| }); |
| |
| it(@"should work on a background scheduler", ^{ |
| testTimer([RACSignal interval:interval onScheduler:[RACScheduler scheduler] withLeeway:leeway], @(interval), @(leeway)); |
| }); |
| }); |
| }); |
| |
| describe(@"-timeout:onScheduler:", ^{ |
| __block RACSubject *subject; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| }); |
| |
| it(@"should time out", ^{ |
| RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; |
| |
| __block NSError *receivedError = nil; |
| [[subject timeout:1 onScheduler:scheduler] subscribeError:^(NSError *e) { |
| receivedError = e; |
| }]; |
| |
| expect(receivedError).to.beNil(); |
| |
| [scheduler stepAll]; |
| expect(receivedError).willNot.beNil(); |
| expect(receivedError.domain).to.equal(RACSignalErrorDomain); |
| expect(receivedError.code).to.equal(RACSignalErrorTimedOut); |
| }); |
| |
| it(@"should pass through events while not timed out", ^{ |
| __block id next = nil; |
| __block BOOL completed = NO; |
| [[subject timeout:1 onScheduler:RACScheduler.mainThreadScheduler] subscribeNext:^(id x) { |
| next = x; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| [subject sendNext:RACUnit.defaultUnit]; |
| expect(next).to.equal(RACUnit.defaultUnit); |
| |
| [subject sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should not time out after disposal", ^{ |
| RACTestScheduler *scheduler = [[RACTestScheduler alloc] init]; |
| |
| __block NSError *receivedError = nil; |
| RACDisposable *disposable = [[subject timeout:1 onScheduler:scheduler] subscribeError:^(NSError *e) { |
| receivedError = e; |
| }]; |
| |
| [disposable dispose]; |
| [scheduler stepAll]; |
| expect(receivedError).to.beNil(); |
| }); |
| }); |
| |
| describe(@"-delay:", ^{ |
| __block RACSubject *subject; |
| __block RACSignal *delayedSignal; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| delayedSignal = [subject delay:0]; |
| }); |
| |
| it(@"should delay nexts", ^{ |
| __block id next = nil; |
| [delayedSignal subscribeNext:^(id x) { |
| next = x; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| expect(next).to.beNil(); |
| expect(next).will.equal(@"foo"); |
| }); |
| |
| it(@"should delay completed", ^{ |
| __block BOOL completed = NO; |
| [delayedSignal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| [subject sendCompleted]; |
| expect(completed).to.beFalsy(); |
| expect(completed).will.beTruthy(); |
| }); |
| |
| it(@"should not delay errors", ^{ |
| __block NSError *error = nil; |
| [delayedSignal subscribeError:^(NSError *e) { |
| error = e; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should cancel delayed events when disposed", ^{ |
| __block id next = nil; |
| RACDisposable *disposable = [delayedSignal subscribeNext:^(id x) { |
| next = x; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| |
| __block BOOL done = NO; |
| [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ |
| done = YES; |
| }]; |
| |
| [disposable dispose]; |
| |
| expect(done).will.beTruthy(); |
| expect(next).to.beNil(); |
| }); |
| }); |
| |
| describe(@"-catch:", ^{ |
| it(@"should subscribe to ensuing signal on error", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| RACSignal *signal = [subject catch:^(NSError *error) { |
| return [RACSignal return:@41]; |
| }]; |
| |
| __block id value = nil; |
| [signal subscribeNext:^(id x) { |
| value = x; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(value).to.equal(@41); |
| }); |
| |
| it(@"should prevent source error from propagating", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| RACSignal *signal = [subject catch:^(NSError *error) { |
| return [RACSignal empty]; |
| }]; |
| |
| __block BOOL errorReceived = NO; |
| [signal subscribeError:^(NSError *error) { |
| errorReceived = YES; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(errorReceived).to.beFalsy(); |
| }); |
| |
| it(@"should propagate error from ensuing signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| NSError *secondaryError = [NSError errorWithDomain:@"bubs" code:41 userInfo:nil]; |
| RACSignal *signal = [subject catch:^(NSError *error) { |
| return [RACSignal error:secondaryError]; |
| }]; |
| |
| __block NSError *errorReceived = nil; |
| [signal subscribeError:^(NSError *error) { |
| errorReceived = error; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(errorReceived).to.equal(secondaryError); |
| }); |
| |
| it(@"should dispose ensuing signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| __block BOOL disposed = NO; |
| RACSignal *signal = [subject catch:^(NSError *error) { |
| return [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| }]; |
| |
| RACDisposable *disposable = [signal subscribeCompleted:^{}]; |
| [subject sendError:RACSignalTestError]; |
| [disposable dispose]; |
| |
| expect(disposed).will.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-try:", ^{ |
| __block RACSubject *subject; |
| __block NSError *receivedError; |
| __block NSMutableArray *nextValues; |
| __block BOOL completed; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| nextValues = [NSMutableArray array]; |
| completed = NO; |
| receivedError = nil; |
| |
| [[subject try:^(NSString *value, NSError **error) { |
| if (value != nil) return YES; |
| |
| if (error != nil) *error = RACSignalTestError; |
| |
| return NO; |
| }] subscribeNext:^(id x) { |
| [nextValues addObject:x]; |
| } error:^(NSError *error) { |
| receivedError = error; |
| } completed:^{ |
| completed = YES; |
| }]; |
| }); |
| |
| it(@"should pass values while YES is returned from the tryBlock", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| [subject sendNext:@"baz"]; |
| [subject sendNext:@"buzz"]; |
| [subject sendCompleted]; |
| |
| NSArray *receivedValues = [nextValues copy]; |
| NSArray *expectedValues = @[ @"foo", @"bar", @"baz", @"buzz" ]; |
| |
| expect(receivedError).to.beNil(); |
| expect(receivedValues).to.equal(expectedValues); |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should pass values until NO is returned from the tryBlock", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| [subject sendNext:nil]; |
| [subject sendNext:@"buzz"]; |
| [subject sendCompleted]; |
| |
| NSArray *receivedValues = [nextValues copy]; |
| NSArray *expectedValues = @[ @"foo", @"bar" ]; |
| |
| expect(receivedError).to.equal(RACSignalTestError); |
| expect(receivedValues).to.equal(expectedValues); |
| expect(completed).to.beFalsy(); |
| }); |
| }); |
| |
| describe(@"-tryMap:", ^{ |
| __block RACSubject *subject; |
| __block NSError *receivedError; |
| __block NSMutableArray *nextValues; |
| __block BOOL completed; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| nextValues = [NSMutableArray array]; |
| completed = NO; |
| receivedError = nil; |
| |
| [[subject tryMap:^ id (NSString *value, NSError **error) { |
| if (value != nil) return [NSString stringWithFormat:@"%@_a", value]; |
| |
| if (error != nil) *error = RACSignalTestError; |
| |
| return nil; |
| }] subscribeNext:^(id x) { |
| [nextValues addObject:x]; |
| } error:^(NSError *error) { |
| receivedError = error; |
| } completed:^{ |
| completed = YES; |
| }]; |
| }); |
| |
| it(@"should map values with the mapBlock", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| [subject sendNext:@"baz"]; |
| [subject sendNext:@"buzz"]; |
| [subject sendCompleted]; |
| |
| NSArray *receivedValues = [nextValues copy]; |
| NSArray *expectedValues = @[ @"foo_a", @"bar_a", @"baz_a", @"buzz_a" ]; |
| |
| expect(receivedError).to.beNil(); |
| expect(receivedValues).to.equal(expectedValues); |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should map values with the mapBlock, until the mapBlock returns nil", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| [subject sendNext:nil]; |
| [subject sendNext:@"buzz"]; |
| [subject sendCompleted]; |
| |
| NSArray *receivedValues = [nextValues copy]; |
| NSArray *expectedValues = @[ @"foo_a", @"bar_a" ]; |
| |
| expect(receivedError).to.equal(RACSignalTestError); |
| expect(receivedValues).to.equal(expectedValues); |
| expect(completed).to.beFalsy(); |
| }); |
| }); |
| |
| describe(@"throttling", ^{ |
| __block RACSubject *subject; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| }); |
| |
| describe(@"-throttle:", ^{ |
| __block RACSignal *throttledSignal; |
| |
| beforeEach(^{ |
| throttledSignal = [subject throttle:0]; |
| }); |
| |
| it(@"should throttle nexts", ^{ |
| NSMutableArray *valuesReceived = [NSMutableArray array]; |
| [throttledSignal subscribeNext:^(id x) { |
| [valuesReceived addObject:x]; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| expect(valuesReceived).to.equal(@[]); |
| |
| NSArray *expected = @[ @"bar" ]; |
| expect(valuesReceived).will.equal(expected); |
| |
| [subject sendNext:@"buzz"]; |
| expect(valuesReceived).to.equal(expected); |
| |
| expected = @[ @"bar", @"buzz" ]; |
| expect(valuesReceived).will.equal(expected); |
| }); |
| |
| it(@"should forward completed immediately", ^{ |
| __block BOOL completed = NO; |
| [throttledSignal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| [subject sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should forward errors immediately", ^{ |
| __block NSError *error = nil; |
| [throttledSignal subscribeError:^(NSError *e) { |
| error = e; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should cancel future nexts when disposed", ^{ |
| __block id next = nil; |
| RACDisposable *disposable = [throttledSignal subscribeNext:^(id x) { |
| next = x; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| |
| __block BOOL done = NO; |
| [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ |
| done = YES; |
| }]; |
| |
| [disposable dispose]; |
| |
| expect(done).will.beTruthy(); |
| expect(next).to.beNil(); |
| }); |
| }); |
| |
| describe(@"-throttle:valuesPassingTest:", ^{ |
| __block RACSignal *throttledSignal; |
| __block BOOL shouldThrottle; |
| |
| beforeEach(^{ |
| shouldThrottle = YES; |
| |
| __block id value = nil; |
| throttledSignal = [[subject |
| doNext:^(id x) { |
| value = x; |
| }] |
| throttle:0 valuesPassingTest:^(id x) { |
| // Make sure that we're given the latest value. |
| expect(x).to.beIdenticalTo(value); |
| |
| return shouldThrottle; |
| }]; |
| |
| expect(throttledSignal).notTo.beNil(); |
| }); |
| |
| describe(@"nexts", ^{ |
| __block NSMutableArray *valuesReceived; |
| __block NSMutableArray *expected; |
| |
| beforeEach(^{ |
| expected = [[NSMutableArray alloc] init]; |
| valuesReceived = [[NSMutableArray alloc] init]; |
| |
| [throttledSignal subscribeNext:^(id x) { |
| [valuesReceived addObject:x]; |
| }]; |
| }); |
| |
| it(@"should forward unthrottled values immediately", ^{ |
| shouldThrottle = NO; |
| [subject sendNext:@"foo"]; |
| |
| [expected addObject:@"foo"]; |
| expect(valuesReceived).to.equal(expected); |
| }); |
| |
| it(@"should delay throttled values", ^{ |
| [subject sendNext:@"bar"]; |
| expect(valuesReceived).to.equal(expected); |
| |
| [expected addObject:@"bar"]; |
| expect(valuesReceived).will.equal(expected); |
| }); |
| |
| it(@"should drop buffered values when a throttled value arrives", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| [subject sendNext:@"buzz"]; |
| expect(valuesReceived).to.equal(expected); |
| |
| [expected addObject:@"buzz"]; |
| expect(valuesReceived).will.equal(expected); |
| }); |
| |
| it(@"should drop buffered values when an immediate value arrives", ^{ |
| [subject sendNext:@"foo"]; |
| [subject sendNext:@"bar"]; |
| |
| shouldThrottle = NO; |
| [subject sendNext:@"buzz"]; |
| [expected addObject:@"buzz"]; |
| expect(valuesReceived).to.equal(expected); |
| |
| // Make sure that nothing weird happens when sending another |
| // throttled value. |
| shouldThrottle = YES; |
| [subject sendNext:@"baz"]; |
| expect(valuesReceived).to.equal(expected); |
| |
| [expected addObject:@"baz"]; |
| expect(valuesReceived).will.equal(expected); |
| }); |
| |
| it(@"should not be resent upon completion", ^{ |
| [subject sendNext:@"bar"]; |
| [expected addObject:@"bar"]; |
| expect(valuesReceived).will.equal(expected); |
| |
| [subject sendCompleted]; |
| expect(valuesReceived).to.equal(expected); |
| }); |
| }); |
| |
| it(@"should forward completed immediately", ^{ |
| __block BOOL completed = NO; |
| [throttledSignal subscribeCompleted:^{ |
| completed = YES; |
| }]; |
| |
| [subject sendCompleted]; |
| expect(completed).to.beTruthy(); |
| }); |
| |
| it(@"should forward errors immediately", ^{ |
| __block NSError *error = nil; |
| [throttledSignal subscribeError:^(NSError *e) { |
| error = e; |
| }]; |
| |
| [subject sendError:RACSignalTestError]; |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should cancel future nexts when disposed", ^{ |
| __block id next = nil; |
| RACDisposable *disposable = [throttledSignal subscribeNext:^(id x) { |
| next = x; |
| }]; |
| |
| [subject sendNext:@"foo"]; |
| |
| __block BOOL done = NO; |
| [RACScheduler.mainThreadScheduler after:[NSDate date] schedule:^{ |
| done = YES; |
| }]; |
| |
| [disposable dispose]; |
| |
| expect(done).will.beTruthy(); |
| expect(next).to.beNil(); |
| }); |
| }); |
| }); |
| |
| describe(@"-then:", ^{ |
| it(@"should continue onto returned signal", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| __block id value = nil; |
| [[subject then:^{ |
| return [RACSignal return:@2]; |
| }] subscribeNext:^(id x) { |
| value = x; |
| }]; |
| |
| [subject sendNext:@1]; |
| |
| // The value shouldn't change until the first signal completes. |
| expect(value).to.beNil(); |
| |
| [subject sendCompleted]; |
| |
| expect(value).to.equal(@2); |
| }); |
| |
| it(@"should sequence even if no next value is sent", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| |
| __block id value = nil; |
| [[subject then:^{ |
| return [RACSignal return:RACUnit.defaultUnit]; |
| }] subscribeNext:^(id x) { |
| value = x; |
| }]; |
| |
| [subject sendCompleted]; |
| |
| expect(value).to.equal(RACUnit.defaultUnit); |
| }); |
| }); |
| |
| describe(@"-sequence", ^{ |
| RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:@2]; |
| [subscriber sendNext:@3]; |
| [subscriber sendNext:@4]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| itShouldBehaveLike(RACSequenceExamples, ^{ |
| return @{ |
| RACSequenceExampleSequence: signal.sequence, |
| RACSequenceExampleExpectedValues: @[ @1, @2, @3, @4 ] |
| }; |
| }); |
| }); |
| |
| it(@"should complete take: even if the original signal doesn't", ^{ |
| RACSignal *sendOne = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| return nil; |
| }]; |
| |
| __block id value = nil; |
| __block BOOL completed = NO; |
| [[sendOne take:1] subscribeNext:^(id received) { |
| value = received; |
| } completed:^{ |
| completed = YES; |
| }]; |
| |
| expect(value).to.equal(RACUnit.defaultUnit); |
| expect(completed).to.beTruthy(); |
| }); |
| |
| describe(@"+zip:", ^{ |
| __block RACSubject *subject1 = nil; |
| __block RACSubject *subject2 = nil; |
| __block BOOL hasSentError = NO; |
| __block BOOL hasSentCompleted = NO; |
| __block RACDisposable *disposable = nil; |
| __block void (^send2NextAndErrorTo1)(void) = nil; |
| __block void (^send3NextAndErrorTo1)(void) = nil; |
| __block void (^send2NextAndCompletedTo2)(void) = nil; |
| __block void (^send3NextAndCompletedTo2)(void) = nil; |
| |
| before(^{ |
| send2NextAndErrorTo1 = [^{ |
| [subject1 sendNext:@1]; |
| [subject1 sendNext:@2]; |
| [subject1 sendError:RACSignalTestError]; |
| } copy]; |
| send3NextAndErrorTo1 = [^{ |
| [subject1 sendNext:@1]; |
| [subject1 sendNext:@2]; |
| [subject1 sendNext:@3]; |
| [subject1 sendError:RACSignalTestError]; |
| } copy]; |
| send2NextAndCompletedTo2 = [^{ |
| [subject2 sendNext:@1]; |
| [subject2 sendNext:@2]; |
| [subject2 sendCompleted]; |
| } copy]; |
| send3NextAndCompletedTo2 = [^{ |
| [subject2 sendNext:@1]; |
| [subject2 sendNext:@2]; |
| [subject2 sendNext:@3]; |
| [subject2 sendCompleted]; |
| } copy]; |
| subject1 = [RACSubject subject]; |
| subject2 = [RACSubject subject]; |
| hasSentError = NO; |
| hasSentCompleted = NO; |
| disposable = [[RACSignal zip:@[ subject1, subject2 ]] subscribeError:^(NSError *error) { |
| hasSentError = YES; |
| } completed:^{ |
| hasSentCompleted = YES; |
| }]; |
| }); |
| |
| after(^{ |
| [disposable dispose]; |
| }); |
| |
| it(@"should complete as soon as no new zipped values are possible", ^{ |
| [subject1 sendNext:@1]; |
| [subject2 sendNext:@1]; |
| expect(hasSentCompleted).to.beFalsy(); |
| |
| [subject1 sendNext:@2]; |
| [subject1 sendCompleted]; |
| expect(hasSentCompleted).to.beFalsy(); |
| |
| [subject2 sendNext:@2]; |
| expect(hasSentCompleted).to.beTruthy(); |
| }); |
| |
| it(@"outcome should not be dependent on order of signals", ^{ |
| [subject2 sendCompleted]; |
| expect(hasSentCompleted).to.beTruthy(); |
| }); |
| |
| it(@"should forward errors sent earlier than (time-wise) and before (position-wise) a complete", ^{ |
| send2NextAndErrorTo1(); |
| send3NextAndCompletedTo2(); |
| expect(hasSentError).to.beTruthy(); |
| expect(hasSentCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should forward errors sent earlier than (time-wise) and after (position-wise) a complete", ^{ |
| send3NextAndErrorTo1(); |
| send2NextAndCompletedTo2(); |
| expect(hasSentError).to.beTruthy(); |
| expect(hasSentCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should forward errors sent later than (time-wise) and before (position-wise) a complete", ^{ |
| send3NextAndCompletedTo2(); |
| send2NextAndErrorTo1(); |
| expect(hasSentError).to.beTruthy(); |
| expect(hasSentCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should ignore errors sent later than (time-wise) and after (position-wise) a complete", ^{ |
| send2NextAndCompletedTo2(); |
| send3NextAndErrorTo1(); |
| expect(hasSentError).to.beFalsy(); |
| expect(hasSentCompleted).to.beTruthy(); |
| }); |
| |
| it(@"should handle signals sending values unevenly", ^{ |
| __block NSError *receivedError = nil; |
| __block BOOL hasCompleted = NO; |
| |
| RACSubject *a = [RACSubject subject]; |
| RACSubject *b = [RACSubject subject]; |
| RACSubject *c = [RACSubject subject]; |
| |
| NSMutableArray *receivedValues = NSMutableArray.array; |
| NSArray *expectedValues = nil; |
| |
| [[RACSignal zip:@[ a, b, c ] reduce:^(NSNumber *a, NSNumber *b, NSNumber *c) { |
| return [NSString stringWithFormat:@"%@%@%@", a, b, c]; |
| }] subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| } error:^(NSError *error) { |
| receivedError = error; |
| } completed:^{ |
| hasCompleted = YES; |
| }]; |
| |
| [a sendNext:@1]; |
| [a sendNext:@2]; |
| [a sendNext:@3]; |
| |
| [b sendNext:@1]; |
| |
| [c sendNext:@1]; |
| [c sendNext:@2]; |
| |
| // a: [===......] |
| // b: [=........] |
| // c: [==.......] |
| |
| expectedValues = @[ @"111" ]; |
| expect(receivedValues).to.equal(expectedValues); |
| expect(receivedError).to.beNil(); |
| expect(hasCompleted).to.beFalsy(); |
| |
| [b sendNext:@2]; |
| [b sendNext:@3]; |
| [b sendNext:@4]; |
| [b sendCompleted]; |
| |
| // a: [===......] |
| // b: [====C....] |
| // c: [==.......] |
| |
| expectedValues = @[ @"111", @"222" ]; |
| expect(receivedValues).to.equal(expectedValues); |
| expect(receivedError).to.beNil(); |
| expect(hasCompleted).to.beFalsy(); |
| |
| [c sendNext:@3]; |
| [c sendNext:@4]; |
| [c sendNext:@5]; |
| [c sendError:RACSignalTestError]; |
| |
| // a: [===......] |
| // b: [====C....] |
| // c: [=====E...] |
| |
| expectedValues = @[ @"111", @"222", @"333" ]; |
| expect(receivedValues).to.equal(expectedValues); |
| expect(receivedError).to.equal(RACSignalTestError); |
| expect(hasCompleted).to.beFalsy(); |
| |
| [a sendNext:@4]; |
| [a sendNext:@5]; |
| [a sendNext:@6]; |
| [a sendNext:@7]; |
| |
| // a: [=======..] |
| // b: [====C....] |
| // c: [=====E...] |
| |
| expectedValues = @[ @"111", @"222", @"333" ]; |
| expect(receivedValues).to.equal(expectedValues); |
| expect(receivedError).to.equal(RACSignalTestError); |
| expect(hasCompleted).to.beFalsy(); |
| }); |
| |
| it(@"should handle multiples of the same side-effecting signal", ^{ |
| __block NSUInteger counter = 0; |
| RACSignal *sideEffectingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| ++counter; |
| [subscriber sendNext:@1]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| RACSignal *combined = [RACSignal zip:@[ sideEffectingSignal, sideEffectingSignal ] reduce:^ NSString * (id x, id y) { |
| return [NSString stringWithFormat:@"%@%@", x, y]; |
| }]; |
| NSMutableArray *receivedValues = NSMutableArray.array; |
| |
| expect(counter).to.equal(0); |
| |
| [combined subscribeNext:^(id x) { |
| [receivedValues addObject:x]; |
| }]; |
| |
| expect(counter).to.equal(2); |
| expect(receivedValues).to.equal(@[ @"11" ]); |
| }); |
| }); |
| |
| describe(@"-sample:", ^{ |
| it(@"should send the latest value when the sampler signal fires", ^{ |
| RACSubject *subject = [RACSubject subject]; |
| RACSubject *sampleSubject = [RACSubject subject]; |
| RACSignal *sampled = [subject sample:sampleSubject]; |
| NSMutableArray *values = [NSMutableArray array]; |
| [sampled subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| [sampleSubject sendNext:RACUnit.defaultUnit]; |
| expect(values).to.equal(@[]); |
| |
| [subject sendNext:@1]; |
| [subject sendNext:@2]; |
| expect(values).to.equal(@[]); |
| |
| [sampleSubject sendNext:RACUnit.defaultUnit]; |
| NSArray *expected = @[ @2 ]; |
| expect(values).to.equal(expected); |
| |
| [subject sendNext:@3]; |
| expect(values).to.equal(expected); |
| |
| [sampleSubject sendNext:RACUnit.defaultUnit]; |
| expected = @[ @2, @3 ]; |
| expect(values).to.equal(expected); |
| |
| [sampleSubject sendNext:RACUnit.defaultUnit]; |
| expected = @[ @2, @3, @3 ]; |
| expect(values).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-collect", ^{ |
| __block RACSubject *subject; |
| __block RACSignal *collected; |
| |
| __block id value; |
| __block BOOL hasCompleted; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| collected = [subject collect]; |
| |
| value = nil; |
| hasCompleted = NO; |
| |
| [collected subscribeNext:^(id x) { |
| value = x; |
| } completed:^{ |
| hasCompleted = YES; |
| }]; |
| }); |
| |
| it(@"should send a single array when the original signal completes", ^{ |
| NSArray *expected = @[ @1, @2, @3 ]; |
| |
| [subject sendNext:@1]; |
| [subject sendNext:@2]; |
| [subject sendNext:@3]; |
| expect(value).to.beNil(); |
| |
| [subject sendCompleted]; |
| expect(value).to.equal(expected); |
| expect(hasCompleted).to.beTruthy(); |
| }); |
| |
| it(@"should add NSNull to an array for nil values", ^{ |
| NSArray *expected = @[ NSNull.null, @1, NSNull.null ]; |
| |
| [subject sendNext:nil]; |
| [subject sendNext:@1]; |
| [subject sendNext:nil]; |
| expect(value).to.beNil(); |
| |
| [subject sendCompleted]; |
| expect(value).to.equal(expected); |
| expect(hasCompleted).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-bufferWithTime:", ^{ |
| __block RACTestScheduler *scheduler; |
| |
| __block RACSubject *input; |
| __block RACSignal *bufferedInput; |
| __block RACTuple *latestValue; |
| |
| beforeEach(^{ |
| scheduler = [[RACTestScheduler alloc] init]; |
| |
| input = [RACSubject subject]; |
| bufferedInput = [input bufferWithTime:1 onScheduler:scheduler]; |
| latestValue = nil; |
| |
| [bufferedInput subscribeNext:^(RACTuple *x) { |
| latestValue = x; |
| }]; |
| }); |
| |
| it(@"should buffer nexts", ^{ |
| [input sendNext:@1]; |
| [input sendNext:@2]; |
| |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@1, @2)); |
| |
| [input sendNext:@3]; |
| [input sendNext:@4]; |
| |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@3, @4)); |
| }); |
| |
| it(@"should not perform buffering until a value is sent", ^{ |
| [input sendNext:@1]; |
| [input sendNext:@2]; |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@1, @2)); |
| |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@1, @2)); |
| |
| [input sendNext:@3]; |
| [input sendNext:@4]; |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@3, @4)); |
| }); |
| |
| it(@"should flush any buffered nexts upon completion", ^{ |
| [input sendNext:@1]; |
| [input sendCompleted]; |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(@1)); |
| }); |
| |
| it(@"should support NSNull values", ^{ |
| [input sendNext:NSNull.null]; |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(NSNull.null)); |
| }); |
| |
| it(@"should buffer nil values", ^{ |
| [input sendNext:nil]; |
| [scheduler stepAll]; |
| expect(latestValue).to.equal(RACTuplePack(nil)); |
| }); |
| }); |
| |
| describe(@"-concat", ^{ |
| __block RACSubject *subject; |
| |
| __block RACSignal *oneSignal; |
| __block RACSignal *twoSignal; |
| __block RACSignal *threeSignal; |
| |
| __block RACSignal *errorSignal; |
| __block RACSignal *completedSignal; |
| |
| beforeEach(^{ |
| subject = [RACReplaySubject subject]; |
| |
| oneSignal = [RACSignal return:@1]; |
| twoSignal = [RACSignal return:@2]; |
| threeSignal = [RACSignal return:@3]; |
| |
| errorSignal = [RACSignal error:RACSignalTestError]; |
| completedSignal = RACSignal.empty; |
| }); |
| |
| it(@"should concatenate the values of inner signals", ^{ |
| [subject sendNext:oneSignal]; |
| [subject sendNext:twoSignal]; |
| [subject sendNext:completedSignal]; |
| [subject sendNext:threeSignal]; |
| |
| NSMutableArray *values = [NSMutableArray array]; |
| [[subject concat] subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| NSArray *expected = @[ @1, @2, @3 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should complete only after all signals complete", ^{ |
| RACReplaySubject *valuesSubject = [RACReplaySubject subject]; |
| |
| [subject sendNext:valuesSubject]; |
| [subject sendCompleted]; |
| |
| [valuesSubject sendNext:@1]; |
| [valuesSubject sendNext:@2]; |
| [valuesSubject sendCompleted]; |
| |
| NSArray *expected = @[ @1, @2 ]; |
| expect([[subject concat] toArray]).to.equal(expected); |
| }); |
| |
| it(@"should pass through errors", ^{ |
| [subject sendNext:errorSignal]; |
| |
| NSError *error = nil; |
| [[subject concat] firstOrDefault:nil success:NULL error:&error]; |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| |
| it(@"should concat signals sent later", ^{ |
| [subject sendNext:oneSignal]; |
| |
| NSMutableArray *values = [NSMutableArray array]; |
| [[subject concat] subscribeNext:^(id x) { |
| [values addObject:x]; |
| }]; |
| |
| NSArray *expected = @[ @1 ]; |
| expect(values).to.equal(expected); |
| |
| [subject sendNext:[twoSignal delay:0]]; |
| |
| expected = @[ @1, @2 ]; |
| expect(values).will.equal(expected); |
| |
| [subject sendNext:threeSignal]; |
| |
| expected = @[ @1, @2, @3 ]; |
| expect(values).to.equal(expected); |
| }); |
| |
| it(@"should dispose the current signal", ^{ |
| __block BOOL disposed = NO; |
| RACSignal *innerSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| |
| RACDisposable *concatDisposable = [[subject concat] subscribeCompleted:^{}]; |
| |
| [subject sendNext:innerSignal]; |
| expect(disposed).notTo.beTruthy(); |
| |
| [concatDisposable dispose]; |
| expect(disposed).to.beTruthy(); |
| }); |
| |
| it(@"should dispose later signals", ^{ |
| __block BOOL disposed = NO; |
| RACSignal *laterSignal = [RACSignal createSignal:^(id<RACSubscriber> subscriber) { |
| return [RACDisposable disposableWithBlock:^{ |
| disposed = YES; |
| }]; |
| }]; |
| |
| RACSubject *firstSignal = [RACSubject subject]; |
| RACSignal *outerSignal = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:firstSignal]; |
| [subscriber sendNext:laterSignal]; |
| return nil; |
| }]; |
| |
| RACDisposable *concatDisposable = [[outerSignal concat] subscribeCompleted:^{}]; |
| |
| [firstSignal sendCompleted]; |
| expect(disposed).notTo.beTruthy(); |
| |
| [concatDisposable dispose]; |
| expect(disposed).to.beTruthy(); |
| }); |
| }); |
| |
| describe(@"-initially:", ^{ |
| __block RACSubject *subject; |
| |
| __block NSUInteger initiallyInvokedCount; |
| __block RACSignal *signal; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| |
| initiallyInvokedCount = 0; |
| signal = [subject initially:^{ |
| ++initiallyInvokedCount; |
| }]; |
| }); |
| |
| it(@"should not run without a subscription", ^{ |
| [subject sendCompleted]; |
| expect(initiallyInvokedCount).to.equal(0); |
| }); |
| |
| it(@"should run on subscription", ^{ |
| [signal subscribe:[RACSubscriber new]]; |
| expect(initiallyInvokedCount).to.equal(1); |
| }); |
| |
| it(@"should re-run for each subscription", ^{ |
| [signal subscribe:[RACSubscriber new]]; |
| [signal subscribe:[RACSubscriber new]]; |
| expect(initiallyInvokedCount).to.equal(2); |
| }); |
| }); |
| |
| describe(@"-finally:", ^{ |
| __block RACSubject *subject; |
| |
| __block BOOL finallyInvoked; |
| __block RACSignal *signal; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| |
| finallyInvoked = NO; |
| signal = [subject finally:^{ |
| finallyInvoked = YES; |
| }]; |
| }); |
| |
| it(@"should not run finally without a subscription", ^{ |
| [subject sendCompleted]; |
| expect(finallyInvoked).to.beFalsy(); |
| }); |
| |
| describe(@"with a subscription", ^{ |
| __block RACDisposable *disposable; |
| |
| beforeEach(^{ |
| disposable = [signal subscribeCompleted:^{}]; |
| }); |
| |
| afterEach(^{ |
| [disposable dispose]; |
| }); |
| |
| it(@"should not run finally upon next", ^{ |
| [subject sendNext:RACUnit.defaultUnit]; |
| expect(finallyInvoked).to.beFalsy(); |
| }); |
| |
| it(@"should run finally upon completed", ^{ |
| [subject sendCompleted]; |
| expect(finallyInvoked).to.beTruthy(); |
| }); |
| |
| it(@"should run finally upon error", ^{ |
| [subject sendError:nil]; |
| expect(finallyInvoked).to.beTruthy(); |
| }); |
| }); |
| }); |
| |
| describe(@"-ignoreValues", ^{ |
| __block RACSubject *subject; |
| |
| __block BOOL gotNext; |
| __block BOOL gotCompleted; |
| __block NSError *receivedError; |
| |
| beforeEach(^{ |
| subject = [RACSubject subject]; |
| |
| gotNext = NO; |
| gotCompleted = NO; |
| receivedError = nil; |
| |
| [[subject ignoreValues] subscribeNext:^(id _) { |
| gotNext = YES; |
| } error:^(NSError *error) { |
| receivedError = error; |
| } completed:^{ |
| gotCompleted = YES; |
| }]; |
| }); |
| |
| it(@"should skip nexts and pass through completed", ^{ |
| [subject sendNext:RACUnit.defaultUnit]; |
| [subject sendCompleted]; |
| |
| expect(gotNext).to.beFalsy(); |
| expect(gotCompleted).to.beTruthy(); |
| expect(receivedError).to.beNil(); |
| }); |
| |
| it(@"should skip nexts and pass through errors", ^{ |
| [subject sendNext:RACUnit.defaultUnit]; |
| [subject sendError:RACSignalTestError]; |
| |
| expect(gotNext).to.beFalsy(); |
| expect(gotCompleted).to.beFalsy(); |
| expect(receivedError).to.equal(RACSignalTestError); |
| }); |
| }); |
| |
| describe(@"-materialize", ^{ |
| it(@"should convert nexts and completed into RACEvents", ^{ |
| NSArray *events = [[[RACSignal return:RACUnit.defaultUnit] materialize] toArray]; |
| NSArray *expected = @[ |
| [RACEvent eventWithValue:RACUnit.defaultUnit], |
| RACEvent.completedEvent |
| ]; |
| |
| expect(events).to.equal(expected); |
| }); |
| |
| it(@"should convert errors into RACEvents and complete", ^{ |
| NSArray *events = [[[RACSignal error:RACSignalTestError] materialize] toArray]; |
| NSArray *expected = @[ [RACEvent eventWithError:RACSignalTestError] ]; |
| expect(events).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-dematerialize", ^{ |
| it(@"should convert nexts from RACEvents", ^{ |
| RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:[RACEvent eventWithValue:@1]]; |
| [subscriber sendNext:[RACEvent eventWithValue:@2]]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| NSArray *expected = @[ @1, @2 ]; |
| expect([[events dematerialize] toArray]).to.equal(expected); |
| }); |
| |
| it(@"should convert completed from a RACEvent", ^{ |
| RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:[RACEvent eventWithValue:@1]]; |
| [subscriber sendNext:RACEvent.completedEvent]; |
| [subscriber sendNext:[RACEvent eventWithValue:@2]]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| NSArray *expected = @[ @1 ]; |
| expect([[events dematerialize] toArray]).to.equal(expected); |
| }); |
| |
| it(@"should convert error from a RACEvent", ^{ |
| RACSignal *events = [RACSignal createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:[RACEvent eventWithError:RACSignalTestError]]; |
| [subscriber sendNext:[RACEvent eventWithValue:@1]]; |
| [subscriber sendCompleted]; |
| return nil; |
| }]; |
| |
| __block NSError *error = nil; |
| expect([[events dematerialize] firstOrDefault:nil success:NULL error:&error]).to.beNil(); |
| expect(error).to.equal(RACSignalTestError); |
| }); |
| }); |
| |
| describe(@"-not", ^{ |
| it(@"should invert every BOOL sent", ^{ |
| RACSubject *subject = [RACReplaySubject subject]; |
| [subject sendNext:@NO]; |
| [subject sendNext:@YES]; |
| [subject sendCompleted]; |
| NSArray *results = [[subject not] toArray]; |
| NSArray *expected = @[ @YES, @NO ]; |
| expect(results).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-and", ^{ |
| it(@"should return YES if all YES values are sent", ^{ |
| RACSubject *subject = [RACReplaySubject subject]; |
| |
| [subject sendNext:RACTuplePack(@YES, @NO, @YES)]; |
| [subject sendNext:RACTuplePack(@NO, @NO, @NO)]; |
| [subject sendNext:RACTuplePack(@YES, @YES, @YES)]; |
| [subject sendCompleted]; |
| |
| NSArray *results = [[subject and] toArray]; |
| NSArray *expected = @[ @NO, @NO, @YES ]; |
| |
| expect(results).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-or", ^{ |
| it(@"should return YES for any YES values sent", ^{ |
| RACSubject *subject = [RACReplaySubject subject]; |
| |
| [subject sendNext:RACTuplePack(@YES, @NO, @YES)]; |
| [subject sendNext:RACTuplePack(@NO, @NO, @NO)]; |
| [subject sendCompleted]; |
| |
| NSArray *results = [[subject or] toArray]; |
| NSArray *expected = @[ @YES, @NO ]; |
| |
| expect(results).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-groupBy:", ^{ |
| it(@"should send completed to all grouped signals.", ^{ |
| RACSubject *subject = [RACReplaySubject subject]; |
| |
| __block NSUInteger groupedSignalCount = 0; |
| __block NSUInteger completedGroupedSignalCount = 0; |
| [[subject groupBy:^(NSNumber *number) { |
| return @(floorf(number.floatValue)); |
| }] subscribeNext:^(RACGroupedSignal *groupedSignal) { |
| ++groupedSignalCount; |
| |
| [groupedSignal subscribeCompleted:^{ |
| ++completedGroupedSignalCount; |
| }]; |
| }]; |
| |
| [subject sendNext:@1]; |
| [subject sendNext:@2]; |
| [subject sendCompleted]; |
| |
| expect(completedGroupedSignalCount).to.equal(groupedSignalCount); |
| }); |
| |
| it(@"should send error to all grouped signals.", ^{ |
| RACSubject *subject = [RACReplaySubject subject]; |
| |
| __block NSUInteger groupedSignalCount = 0; |
| __block NSUInteger erroneousGroupedSignalCount = 0; |
| [[subject groupBy:^(NSNumber *number) { |
| return @(floorf(number.floatValue)); |
| }] subscribeNext:^(RACGroupedSignal *groupedSignal) { |
| ++groupedSignalCount; |
| |
| [groupedSignal subscribeError:^(NSError *error) { |
| ++erroneousGroupedSignalCount; |
| |
| expect(error.domain).to.equal(@"TestDomain"); |
| expect(error.code).to.equal(123); |
| }]; |
| }]; |
| |
| [subject sendNext:@1]; |
| [subject sendNext:@2]; |
| [subject sendError:[NSError errorWithDomain:@"TestDomain" code:123 userInfo:nil]]; |
| |
| expect(erroneousGroupedSignalCount).to.equal(groupedSignalCount); |
| }); |
| }); |
| |
| describe(@"starting signals", ^{ |
| describe(@"+startLazilyWithScheduler:block:", ^{ |
| itBehavesLike(RACSignalStartSharedExamplesName, ^{ |
| NSArray *expectedValues = @[ @42, @43 ]; |
| RACScheduler *scheduler = [RACScheduler scheduler]; |
| RACSignal *signal = [RACSignal startLazilyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { |
| for (id value in expectedValues) { |
| [subscriber sendNext:value]; |
| } |
| [subscriber sendCompleted]; |
| }]; |
| return @{ |
| RACSignalStartSignal: signal, |
| RACSignalStartExpectedValues: expectedValues, |
| RACSignalStartExpectedScheduler: scheduler, |
| }; |
| }); |
| |
| __block NSUInteger invokedCount = 0; |
| __block void (^subscribe)(void); |
| |
| beforeEach(^{ |
| invokedCount = 0; |
| |
| RACSignal *signal = [RACSignal startLazilyWithScheduler:RACScheduler.immediateScheduler block:^(id<RACSubscriber> subscriber) { |
| invokedCount++; |
| [subscriber sendNext:@42]; |
| [subscriber sendCompleted]; |
| }]; |
| |
| subscribe = [^{ |
| [signal subscribe:[RACSubscriber subscriberWithNext:nil error:nil completed:nil]]; |
| } copy]; |
| }); |
| |
| it(@"should only invoke the block on subscription", ^{ |
| expect(invokedCount).to.equal(0); |
| subscribe(); |
| expect(invokedCount).to.equal(1); |
| }); |
| |
| it(@"should only invoke the block once", ^{ |
| expect(invokedCount).to.equal(0); |
| subscribe(); |
| expect(invokedCount).to.equal(1); |
| subscribe(); |
| expect(invokedCount).to.equal(1); |
| subscribe(); |
| expect(invokedCount).to.equal(1); |
| }); |
| |
| it(@"should invoke the block on the given scheduler", ^{ |
| RACScheduler *scheduler = [RACScheduler scheduler]; |
| __block RACScheduler *currentScheduler; |
| [[[RACSignal |
| startLazilyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { |
| currentScheduler = RACScheduler.currentScheduler; |
| }] |
| publish] |
| connect]; |
| |
| expect(currentScheduler).will.equal(scheduler); |
| }); |
| }); |
| |
| describe(@"+startEagerlyWithScheduler:block:", ^{ |
| itBehavesLike(RACSignalStartSharedExamplesName, ^{ |
| NSArray *expectedValues = @[ @42, @43 ]; |
| RACScheduler *scheduler = [RACScheduler scheduler]; |
| RACSignal *signal = [RACSignal startEagerlyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { |
| for (id value in expectedValues) { |
| [subscriber sendNext:value]; |
| } |
| [subscriber sendCompleted]; |
| }]; |
| return @{ |
| RACSignalStartSignal: signal, |
| RACSignalStartExpectedValues: expectedValues, |
| RACSignalStartExpectedScheduler: scheduler, |
| }; |
| }); |
| |
| it(@"should immediately invoke the block", ^{ |
| __block BOOL blockInvoked = NO; |
| [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { |
| blockInvoked = YES; |
| }]; |
| |
| expect(blockInvoked).will.beTruthy(); |
| }); |
| |
| it(@"should only invoke the block once", ^{ |
| __block NSUInteger invokedCount = 0; |
| RACSignal *signal = [RACSignal startEagerlyWithScheduler:RACScheduler.immediateScheduler block:^(id<RACSubscriber> subscriber) { |
| invokedCount++; |
| }]; |
| |
| expect(invokedCount).to.equal(1); |
| |
| [[signal publish] connect]; |
| expect(invokedCount).to.equal(1); |
| |
| [[signal publish] connect]; |
| expect(invokedCount).to.equal(1); |
| }); |
| |
| it(@"should invoke the block on the given scheduler", ^{ |
| RACScheduler *scheduler = [RACScheduler scheduler]; |
| __block RACScheduler *currentScheduler; |
| [RACSignal startEagerlyWithScheduler:scheduler block:^(id<RACSubscriber> subscriber) { |
| currentScheduler = RACScheduler.currentScheduler; |
| }]; |
| |
| expect(currentScheduler).will.equal(scheduler); |
| }); |
| }); |
| }); |
| |
| describe(@"-toArray", ^{ |
| __block RACSubject *subject; |
| |
| beforeEach(^{ |
| subject = [RACReplaySubject subject]; |
| }); |
| |
| it(@"should return an array which contains NSNulls for nil values", ^{ |
| NSArray *expected = @[ NSNull.null, @1, NSNull.null ]; |
| |
| [subject sendNext:nil]; |
| [subject sendNext:@1]; |
| [subject sendNext:nil]; |
| [subject sendCompleted]; |
| |
| expect([subject toArray]).to.equal(expected); |
| }); |
| |
| it(@"should return nil upon error", ^{ |
| [subject sendError:nil]; |
| expect([subject toArray]).to.beNil(); |
| }); |
| |
| it(@"should return nil upon error even if some nexts were sent", ^{ |
| [subject sendNext:@1]; |
| [subject sendNext:@2]; |
| [subject sendError:nil]; |
| |
| expect([subject toArray]).to.beNil(); |
| }); |
| }); |
| |
| describe(@"-ignore:", ^{ |
| it(@"should ignore nil", ^{ |
| RACSignal *signal = [[RACSignal |
| createSignal:^ id (id<RACSubscriber> subscriber) { |
| [subscriber sendNext:@1]; |
| [subscriber sendNext:nil]; |
| [subscriber sendNext:@3]; |
| [subscriber sendNext:@4]; |
| [subscriber sendNext:nil]; |
| [subscriber sendCompleted]; |
| return nil; |
| }] |
| ignore:nil]; |
| |
| NSArray *expected = @[ @1, @3, @4 ]; |
| expect([signal toArray]).to.equal(expected); |
| }); |
| }); |
| |
| describe(@"-replayLazily", ^{ |
| __block NSUInteger subscriptionCount; |
| __block BOOL disposed; |
| |
| __block RACSignal *signal; |
| __block RACSubject *disposeSubject; |
| __block RACSignal *replayedSignal; |
| |
| beforeEach(^{ |
| subscriptionCount = 0; |
| disposed = NO; |
| |
| signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { |
| subscriptionCount++; |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| |
| RACDisposable *schedulingDisposable = [RACScheduler.mainThreadScheduler schedule:^{ |
| [subscriber sendNext:RACUnit.defaultUnit]; |
| [subscriber sendCompleted]; |
| }]; |
| |
| return [RACDisposable disposableWithBlock:^{ |
| [schedulingDisposable dispose]; |
| disposed = YES; |
| }]; |
| }]; |
| |
| disposeSubject = [RACSubject subject]; |
| replayedSignal = [[signal takeUntil:disposeSubject] replayLazily]; |
| }); |
| |
| it(@"should forward the input signal upon subscription", ^{ |
| expect(subscriptionCount).to.equal(0); |
| |
| expect([replayedSignal asynchronouslyWaitUntilCompleted:NULL]).to.beTruthy(); |
| expect(subscriptionCount).to.equal(1); |
| }); |
| |
| it(@"should replay the input signal for future subscriptions", ^{ |
| NSArray *events = [[[replayedSignal materialize] collect] asynchronousFirstOrDefault:nil success:NULL error:NULL]; |
| expect(events).notTo.beNil(); |
| |
| expect([[[replayedSignal materialize] collect] asynchronousFirstOrDefault:nil success:NULL error:NULL]).to.equal(events); |
| expect(subscriptionCount).to.equal(1); |
| }); |
| |
| it(@"should replay even after disposal", ^{ |
| __block NSUInteger valueCount = 0; |
| [replayedSignal subscribeNext:^(id x) { |
| valueCount++; |
| }]; |
| |
| [disposeSubject sendCompleted]; |
| expect(valueCount).to.equal(1); |
| expect([[replayedSignal toArray] count]).to.equal(valueCount); |
| }); |
| }); |
| |
| SpecEnd |