| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- //
- // RACSubjectSpec.m
- // ReactiveCocoa
- //
- // Created by Josh Abernathy on 6/24/12.
- // Copyright (c) 2012 GitHub, Inc. All rights reserved.
- //
- @import Quick;
- @import Nimble;
- #import "RACSubscriberExamples.h"
- #import <libkern/OSAtomic.h>
- #import <ReactiveCocoa/EXTScope.h>
- #import "RACBehaviorSubject.h"
- #import "RACCompoundDisposable.h"
- #import "RACDisposable.h"
- #import "RACReplaySubject.h"
- #import "RACScheduler.h"
- #import "RACSignal+Operations.h"
- #import "RACSubject.h"
- #import "RACUnit.h"
- @interface RACTestSubscriber : NSObject <RACSubscriber>
- @property (nonatomic, strong, readonly) RACDisposable *disposable;
- @end
- @implementation RACTestSubscriber
- - (instancetype)init {
- self = [super init];
- _disposable = [RACDisposable new];
- return self;
- }
- - (void)sendNext:(id)value {}
- - (void)sendError:(NSError *)error {}
- - (void)sendCompleted {}
- - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
- [disposable addDisposable:self.disposable];
- }
- @end
- QuickSpecBegin(RACSubjectSpec)
- qck_describe(@"RACSubject", ^{
- __block RACSubject *subject;
- __block NSMutableArray *values;
- __block BOOL success;
- __block NSError *error;
- qck_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;
- }];
- });
- qck_it(@"should dispose the paired disposable when a subscription terminates", ^{
- RACSubject* subject = [RACSubject new];
- RACTestSubscriber* subscriber = [RACTestSubscriber new];
- [[subject subscribe:subscriber] dispose];
- expect(@(subscriber.disposable.disposed)).to(beTruthy());
- });
- qck_itBehavesLike(RACSubscriberExamples, ^{
- return @{
- RACSubscriberExampleSubscriber: subject,
- RACSubscriberExampleValuesReceivedBlock: [^{ return [values copy]; } copy],
- RACSubscriberExampleErrorReceivedBlock: [^{ return error; } copy],
- RACSubscriberExampleSuccessBlock: [^{ return success; } copy]
- };
- });
- });
- qck_describe(@"RACReplaySubject", ^{
- __block RACReplaySubject *subject = nil;
- qck_describe(@"with a capacity of 1", ^{
- qck_beforeEach(^{
- subject = [RACReplaySubject replaySubjectWithCapacity:1];
- });
- qck_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));
- });
- qck_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));
- });
- qck_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());
- });
- qck_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());
- });
- qck_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());
- });
- });
- qck_describe(@"with an unlimited capacity", ^{
- qck_beforeEach(^{
- subject = [RACReplaySubject subject];
- });
- qck_itBehavesLike(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]
- };
- });
- qck_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).to(haveCount(@2));
- NSArray *expected = [NSArray arrayWithObjects:firstValue, secondValue, nil];
- expect(valuesReceived).to(equal(expected));
- expect(@(completed)).to(beTruthy());
- });
- qck_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("org.reactivecocoa.ReactiveCocoa.RACSubjectSpec", DISPATCH_QUEUE_CONCURRENT);
- 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).to(haveCount(@(count)));
- NSArray *replayedValues = subject.toArray;
- expect(replayedValues).to(haveCount(@(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));
- }];
- });
- qck_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).toEventuallyNot(beNil());
- });
- qck_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).toEventually(equal(@[ @0 ]));
- });
- qck_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).toEventually(equal(@1));
- });
- qck_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).toEventually(equal(@1));
- });
- qck_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).toEventually(equal(expected));
- });
- });
- });
- QuickSpecEnd
|