| // | |
| // RACSubjectSpec.m | |
| // ReactiveCocoa | |
| // | |
| // Created by Josh Abernathy on 6/24/12. | |
| // Copyright (c) 2012 GitHub, Inc. All rights reserved. | |
| // | |
| #import "RACSubscriberExamples.h" | |
| #import <libkern/OSAtomic.h> | |
| #import "EXTScope.h" | |
| #import "RACBehaviorSubject.h" | |
| #import "RACDisposable.h" | |
| #import "RACReplaySubject.h" | |
| #import "RACScheduler.h" | |
| #import "RACSignal+Operations.h" | |
| #import "RACSubject.h" | |
| #import "RACUnit.h" | |
| SpecBegin(RACSubject) | |
| describe(@"RACSubject", ^{ | |
| __block RACSubject *subject; | |
| __block NSMutableArray *values; | |
| __block BOOL success; | |
| __block NSError *error; | |
| beforeEach(^{ | |
| values = [NSMutableArray array]; | |
| subject = [RACSubject subject]; | |
| success = YES; | |
| error = nil; | |
| [subject subscribeNext:^(id value) { | |
| [values addObject:value]; | |
| } error:^(NSError *e) { | |
| error = e; | |
| success = NO; | |
| } completed:^{ | |
| success = YES; | |
| }]; | |
| }); | |
| itShouldBehaveLike(RACSubscriberExamples, ^{ | |
| return @{ | |
| RACSubscriberExampleSubscriber: subject, | |
| RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy], | |
| RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy], | |
| RACSubscriberExampleSuccessBlock: [^{ return success; } copy] | |
| }; | |
| }); | |
| }); | |
| describe(@"RACReplaySubject", ^{ | |
| __block RACReplaySubject *subject = nil; | |
| describe(@"with a capacity of 1", ^{ | |
| beforeEach(^{ | |
| subject = [RACReplaySubject replaySubjectWithCapacity:1]; | |
| }); | |
| it(@"should send the last value", ^{ | |
| id firstValue = @"blah"; | |
| id secondValue = @"more blah"; | |
| [subject sendNext:firstValue]; | |
| [subject sendNext:secondValue]; | |
| __block id valueReceived = nil; | |
| [subject subscribeNext:^(id x) { | |
| valueReceived = x; | |
| }]; | |
| expect(valueReceived).to.equal(secondValue); | |
| }); | |
| it(@"should send the last value to new subscribers after completion", ^{ | |
| id firstValue = @"blah"; | |
| id secondValue = @"more blah"; | |
| __block id valueReceived = nil; | |
| __block NSUInteger nextsReceived = 0; | |
| [subject sendNext:firstValue]; | |
| [subject sendNext:secondValue]; | |
| expect(nextsReceived).to.equal(0); | |
| expect(valueReceived).to.beNil(); | |
| [subject sendCompleted]; | |
| [subject subscribeNext:^(id x) { | |
| valueReceived = x; | |
| nextsReceived++; | |
| }]; | |
| expect(nextsReceived).to.equal(1); | |
| expect(valueReceived).to.equal(secondValue); | |
| }); | |
| it(@"should not send any values to new subscribers if none were sent originally", ^{ | |
| [subject sendCompleted]; | |
| __block BOOL nextInvoked = NO; | |
| [subject subscribeNext:^(id x) { | |
| nextInvoked = YES; | |
| }]; | |
| expect(nextInvoked).to.beFalsy(); | |
| }); | |
| it(@"should resend errors", ^{ | |
| NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]; | |
| [subject sendError:error]; | |
| __block BOOL errorSent = NO; | |
| [subject subscribeError:^(NSError *sentError) { | |
| expect(sentError).to.equal(error); | |
| errorSent = YES; | |
| }]; | |
| expect(errorSent).to.beTruthy(); | |
| }); | |
| it(@"should resend nil errors", ^{ | |
| [subject sendError:nil]; | |
| __block BOOL errorSent = NO; | |
| [subject subscribeError:^(NSError *sentError) { | |
| expect(sentError).to.beNil(); | |
| errorSent = YES; | |
| }]; | |
| expect(errorSent).to.beTruthy(); | |
| }); | |
| }); | |
| describe(@"with an unlimited capacity", ^{ | |
| beforeEach(^{ | |
| subject = [RACReplaySubject subject]; | |
| }); | |
| itShouldBehaveLike(RACSubscriberExamples, ^{ | |
| return @{ | |
| RACSubscriberExampleSubscriber: subject, | |
| RACSubscriberExampleValuesReceivedBlock: [^{ | |
| NSMutableArray *values = [NSMutableArray array]; | |
| // This subscription should synchronously dump all values already | |
| // received into 'values'. | |
| [subject subscribeNext:^(id value) { | |
| [values addObject:value]; | |
| }]; | |
| return values; | |
| } copy], | |
| RACSubscriberExampleErrorReceivedBlock: [^{ | |
| __block NSError *error = nil; | |
| [subject subscribeError:^(NSError *x) { | |
| error = x; | |
| }]; | |
| return error; | |
| } copy], | |
| RACSubscriberExampleSuccessBlock: [^{ | |
| __block BOOL success = YES; | |
| [subject subscribeError:^(NSError *x) { | |
| success = NO; | |
| }]; | |
| return success; | |
| } copy] | |
| }; | |
| }); | |
| it(@"should send both values to new subscribers after completion", ^{ | |
| id firstValue = @"blah"; | |
| id secondValue = @"more blah"; | |
| [subject sendNext:firstValue]; | |
| [subject sendNext:secondValue]; | |
| [subject sendCompleted]; | |
| __block BOOL completed = NO; | |
| NSMutableArray *valuesReceived = [NSMutableArray array]; | |
| [subject subscribeNext:^(id x) { | |
| [valuesReceived addObject:x]; | |
| } completed:^{ | |
| completed = YES; | |
| }]; | |
| expect(valuesReceived.count).to.equal(2); | |
| NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil]; | |
| expect(valuesReceived).to.equal(expected); | |
| expect(completed).to.beTruthy(); | |
| }); | |
| it(@"should send values in the same order live as when replaying", ^{ | |
| NSUInteger count = 49317; | |
| // Just leak it, ain't no thang. | |
| __unsafe_unretained volatile id *values = (__unsafe_unretained id *)calloc(count, sizeof(*values)); | |
| __block volatile int32_t nextIndex = 0; | |
| [subject subscribeNext:^(NSNumber *value) { | |
| int32_t indexPlusOne = OSAtomicIncrement32(&nextIndex); | |
| values[indexPlusOne - 1] = value; | |
| }]; | |
| dispatch_queue_t queue = dispatch_queue_create("com.github.ReactiveCocoa.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT); | |
| @onExit { | |
| dispatch_release(queue); | |
| }; | |
| dispatch_suspend(queue); | |
| for (NSUInteger i = 0; i < count; i++) { | |
| dispatch_async(queue, ^{ | |
| [subject sendNext:@(i)]; | |
| }); | |
| } | |
| dispatch_resume(queue); | |
| dispatch_barrier_sync(queue, ^{ | |
| [subject sendCompleted]; | |
| }); | |
| OSMemoryBarrier(); | |
| NSArray *liveValues = [NSArray arrayWithObjects:(id *)values count:(NSUInteger)nextIndex]; | |
| expect(liveValues.count).to.equal(count); | |
| NSArray *replayedValues = subject.toArray; | |
| expect(replayedValues.count).to.equal(count); | |
| // It should return the same ordering for multiple invocations too. | |
| expect(replayedValues).to.equal(subject.toArray); | |
| [replayedValues enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) { | |
| expect(liveValues[index]).to.equal(value); | |
| }]; | |
| }); | |
| it(@"should have a current scheduler when replaying", ^{ | |
| [subject sendNext:RACUnit.defaultUnit]; | |
| __block RACScheduler *currentScheduler; | |
| [subject subscribeNext:^(id x) { | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| expect(currentScheduler).notTo.beNil(); | |
| currentScheduler = nil; | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| [subject subscribeNext:^(id x) { | |
| currentScheduler = RACScheduler.currentScheduler; | |
| }]; | |
| }); | |
| expect(currentScheduler).willNot.beNil(); | |
| }); | |
| it(@"should stop replaying when the subscription is disposed", ^{ | |
| NSMutableArray *values = [NSMutableArray array]; | |
| [subject sendNext:@0]; | |
| [subject sendNext:@1]; | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| __block RACDisposable *disposable = [subject subscribeNext:^(id x) { | |
| expect(disposable).notTo.beNil(); | |
| [values addObject:x]; | |
| [disposable dispose]; | |
| }]; | |
| }); | |
| expect(values).will.equal(@[ @0 ]); | |
| }); | |
| it(@"should finish replaying before completing", ^{ | |
| [subject sendNext:@1]; | |
| __block id received; | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| [subject subscribeNext:^(id x) { | |
| received = x; | |
| }]; | |
| [subject sendCompleted]; | |
| }); | |
| expect(received).will.equal(@1); | |
| }); | |
| it(@"should finish replaying before erroring", ^{ | |
| [subject sendNext:@1]; | |
| __block id received; | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| [subject subscribeNext:^(id x) { | |
| received = x; | |
| }]; | |
| [subject sendError:[NSError errorWithDomain:@"blah" code:-99 userInfo:nil]]; | |
| }); | |
| expect(received).will.equal(@1); | |
| }); | |
| it(@"should finish replaying before sending new values", ^{ | |
| [subject sendNext:@1]; | |
| NSMutableArray *received = [NSMutableArray array]; | |
| dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | |
| [subject subscribeNext:^(id x) { | |
| [received addObject:x]; | |
| }]; | |
| [subject sendNext:@2]; | |
| }); | |
| NSArray *expected = @[ @1, @2 ]; | |
| expect(received).will.equal(expected); | |
| }); | |
| }); | |
| }); | |
| SpecEnd |