| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- //
- // RACCommandSpec.m
- // ReactiveCocoa
- //
- // Created by Josh Abernathy on 8/31/12.
- // Copyright (c) 2012 GitHub, Inc. All rights reserved.
- //
- #import <Quick/Quick.h>
- #import <Nimble/Nimble.h>
- #import "NSArray+RACSequenceAdditions.h"
- #import "NSObject+RACDeallocating.h"
- #import "NSObject+RACPropertySubscribing.h"
- #import "RACCommand.h"
- #import "RACCompoundDisposable.h"
- #import "RACDisposable.h"
- #import "RACEvent.h"
- #import "RACScheduler.h"
- #import "RACSequence.h"
- #import "RACSignal+Operations.h"
- #import "RACSubject.h"
- #import "RACUnit.h"
- QuickSpecBegin(RACCommandSpec)
- RACSignal * (^emptySignalBlock)(id) = ^(id _) {
- return [RACSignal empty];
- };
- qck_describe(@"with a simple signal block", ^{
- __block RACCommand *command;
- qck_beforeEach(^{
- command = [[RACCommand alloc] initWithSignalBlock:^(id value) {
- return [RACSignal return:value];
- }];
- expect(command).notTo(beNil());
- expect(@(command.allowsConcurrentExecution)).to(beFalsy());
- });
- qck_it(@"should be enabled by default", ^{
- expect([command.enabled first]).to(equal(@YES));
- });
- qck_it(@"should not be executing by default", ^{
- expect([command.executing first]).to(equal(@NO));
- });
- qck_it(@"should create an execution signal", ^{
- __block NSUInteger signalsReceived = 0;
- __block BOOL completed = NO;
- id value = NSNull.null;
- [command.executionSignals subscribeNext:^(RACSignal *signal) {
- signalsReceived++;
- [signal subscribeNext:^(id x) {
- expect(x).to(equal(value));
- } completed:^{
- completed = YES;
- }];
- }];
- expect(@(signalsReceived)).to(equal(@0));
-
- [command execute:value];
- expect(@(signalsReceived)).toEventually(equal(@1));
- expect(@(completed)).to(beTruthy());
- });
- qck_it(@"should return the execution signal from -execute:", ^{
- __block BOOL completed = NO;
- id value = NSNull.null;
- [[command
- execute:value]
- subscribeNext:^(id x) {
- expect(x).to(equal(value));
- } completed:^{
- completed = YES;
- }];
- expect(@(completed)).toEventually(beTruthy());
- });
- qck_it(@"should always send executionSignals on the main thread", ^{
- __block RACScheduler *receivedScheduler = nil;
- [command.executionSignals subscribeNext:^(id _) {
- receivedScheduler = RACScheduler.currentScheduler;
- }];
- [[RACScheduler scheduler] schedule:^{
- expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy());
- }];
- expect(receivedScheduler).to(beNil());
- expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- });
- qck_it(@"should not send anything on 'errors' by default", ^{
- __block BOOL receivedError = NO;
- [command.errors subscribeNext:^(id _) {
- receivedError = YES;
- }];
-
- expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- expect(@(receivedError)).to(beFalsy());
- });
- qck_it(@"should be executing while an execution signal is running", ^{
- [command.executionSignals subscribeNext:^(RACSignal *signal) {
- [signal subscribeNext:^(id x) {
- expect([command.executing first]).to(equal(@YES));
- }];
- }];
- expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- expect([command.executing first]).to(equal(@NO));
- });
- qck_it(@"should always update executing on the main thread", ^{
- __block RACScheduler *updatedScheduler = nil;
- [[command.executing skip:1] subscribeNext:^(NSNumber *executing) {
- if (!executing.boolValue) return;
- updatedScheduler = RACScheduler.currentScheduler;
- }];
- [[RACScheduler scheduler] schedule:^{
- expect(@([[command execute:nil] waitUntilCompleted:NULL])).to(beTruthy());
- }];
- expect([command.executing first]).to(equal(@NO));
- expect(updatedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- });
- qck_it(@"should dealloc without subscribers", ^{
- __block BOOL disposed = NO;
- @autoreleasepool {
- RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock];
- [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
- disposed = YES;
- }]];
- }
- expect(@(disposed)).toEventually(beTruthy());
- });
- qck_it(@"should complete signals on the main thread when deallocated", ^{
- __block RACScheduler *executionSignalsScheduler = nil;
- __block RACScheduler *executingScheduler = nil;
- __block RACScheduler *enabledScheduler = nil;
- __block RACScheduler *errorsScheduler = nil;
- [[RACScheduler scheduler] schedule:^{
- @autoreleasepool {
- RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithSignalBlock:emptySignalBlock];
- [command.executionSignals subscribeCompleted:^{
- executionSignalsScheduler = RACScheduler.currentScheduler;
- }];
- [command.executing subscribeCompleted:^{
- executingScheduler = RACScheduler.currentScheduler;
- }];
- [command.enabled subscribeCompleted:^{
- enabledScheduler = RACScheduler.currentScheduler;
- }];
- [command.errors subscribeCompleted:^{
- errorsScheduler = RACScheduler.currentScheduler;
- }];
- }
- }];
- expect(executionSignalsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- expect(executingScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- expect(enabledScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- expect(errorsScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- });
- });
- qck_it(@"should invoke the signalBlock once per execution", ^{
- NSMutableArray *valuesReceived = [NSMutableArray array];
- RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id x) {
- [valuesReceived addObject:x];
- return [RACSignal empty];
- }];
- expect(@([[command execute:@"foo"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- expect(valuesReceived).to(equal((@[ @"foo" ])));
- expect(@([[command execute:@"bar"] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- expect(valuesReceived).to(equal((@[ @"foo", @"bar" ])));
- });
- qck_it(@"should send on executionSignals in order of execution", ^{
- RACCommand<RACSequence *> *command = [[RACCommand alloc] initWithSignalBlock:^(RACSequence *seq) {
- return [seq signalWithScheduler:RACScheduler.immediateScheduler];
- }];
- NSMutableArray *valuesReceived = [NSMutableArray array];
- [[command.executionSignals
- concat]
- subscribeNext:^(id x) {
- [valuesReceived addObject:x];
- }];
- RACSequence *first = @[ @"foo", @"bar" ].rac_sequence;
- expect(@([[command execute:first] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- RACSequence *second = @[ @"buzz", @"baz" ].rac_sequence;
- expect(@([[command execute:second] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- NSArray *expectedValues = @[ @"foo", @"bar", @"buzz", @"baz" ];
- expect(valuesReceived).to(equal(expectedValues));
- });
- qck_it(@"should wait for all signals to complete or error before executing sends NO", ^{
- RACCommand<RACSignal *> *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
- return signal;
- }];
- command.allowsConcurrentExecution = YES;
-
- RACSubject *firstSubject = [RACSubject subject];
- expect([command execute:firstSubject]).notTo(beNil());
- RACSubject *secondSubject = [RACSubject subject];
- expect([command execute:secondSubject]).notTo(beNil());
- expect([command.executing first]).toEventually(equal(@YES));
- [firstSubject sendError:nil];
- expect([command.executing first]).to(equal(@YES));
- [secondSubject sendNext:nil];
- expect([command.executing first]).to(equal(@YES));
- [secondSubject sendCompleted];
- expect([command.executing first]).toEventually(equal(@NO));
- });
- qck_it(@"should have allowsConcurrentExecution be observable", ^{
- RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
- return signal;
- }];
-
- RACSubject *completion = [RACSubject subject];
- RACSignal *allowsConcurrentExecution = [[RACObserve(command, allowsConcurrentExecution)
- takeUntil:completion]
- replayLast];
-
- command.allowsConcurrentExecution = YES;
-
- expect([allowsConcurrentExecution first]).to(beTrue());
- [completion sendCompleted];
- });
- qck_it(@"should not deliver errors from executionSignals", ^{
- RACSubject *subject = [RACSubject subject];
- NSMutableArray *receivedEvents = [NSMutableArray array];
- RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
- return subject;
- }];
- [[[command.executionSignals
- flatten]
- materialize]
- subscribeNext:^(RACEvent *event) {
- [receivedEvents addObject:event];
- }];
- expect([command execute:nil]).notTo(beNil());
- expect([command.executing first]).toEventually(equal(@YES));
- [subject sendNext:RACUnit.defaultUnit];
- NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ];
- expect(receivedEvents).toEventually(equal(expectedEvents));
- expect([command.executing first]).to(equal(@YES));
- [subject sendNext:@"foo"];
- expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ];
- expect(receivedEvents).toEventually(equal(expectedEvents));
- expect([command.executing first]).to(equal(@YES));
- NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];
- [subject sendError:error];
- expect([command.executing first]).toEventually(equal(@NO));
- expect(receivedEvents).to(equal(expectedEvents));
- });
- qck_it(@"should deliver errors from -execute:", ^{
- RACSubject *subject = [RACSubject subject];
- NSMutableArray *receivedEvents = [NSMutableArray array];
- RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
- return subject;
- }];
- [[[command
- execute:nil]
- materialize]
- subscribeNext:^(RACEvent *event) {
- [receivedEvents addObject:event];
- }];
- expect([command.executing first]).toEventually(equal(@YES));
- [subject sendNext:RACUnit.defaultUnit];
- NSArray *expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit] ];
- expect(receivedEvents).toEventually(equal(expectedEvents));
- expect([command.executing first]).to(equal(@YES));
- [subject sendNext:@"foo"];
- expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"] ];
- expect(receivedEvents).toEventually(equal(expectedEvents));
- expect([command.executing first]).to(equal(@YES));
- NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];
- [subject sendError:error];
- expectedEvents = @[ [RACEvent eventWithValue:RACUnit.defaultUnit], [RACEvent eventWithValue:@"foo"], [RACEvent eventWithError:error] ];
- expect(receivedEvents).toEventually(equal(expectedEvents));
- expect([command.executing first]).toEventually(equal(@NO));
- });
- qck_it(@"should deliver errors onto 'errors'", ^{
- RACCommand<RACSignal *> *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
- return signal;
- }];
- command.allowsConcurrentExecution = YES;
-
- RACSubject *firstSubject = [RACSubject subject];
- expect([command execute:firstSubject]).notTo(beNil());
- RACSubject *secondSubject = [RACSubject subject];
- expect([command execute:secondSubject]).notTo(beNil());
- NSError *firstError = [NSError errorWithDomain:@"" code:1 userInfo:nil];
- NSError *secondError = [NSError errorWithDomain:@"" code:2 userInfo:nil];
-
- // We should receive errors from our previously-started executions.
- NSMutableArray *receivedErrors = [NSMutableArray array];
- [command.errors subscribeNext:^(NSError *error) {
- [receivedErrors addObject:error];
- }];
- expect([command.executing first]).toEventually(equal(@YES));
- [firstSubject sendError:firstError];
- expect([command.executing first]).toEventually(equal(@YES));
- NSArray *expected = @[ firstError ];
- expect(receivedErrors).toEventually(equal(expected));
- [secondSubject sendError:secondError];
- expect([command.executing first]).toEventually(equal(@NO));
- expected = @[ firstError, secondError ];
- expect(receivedErrors).toEventually(equal(expected));
- });
- qck_it(@"should not deliver non-error events onto 'errors'", ^{
- RACSubject *subject = [RACSubject subject];
- RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
- return subject;
- }];
- __block BOOL receivedEvent = NO;
- [command.errors subscribeNext:^(id _) {
- receivedEvent = YES;
- }];
- expect([command execute:nil]).notTo(beNil());
- expect([command.executing first]).toEventually(equal(@YES));
- [subject sendNext:RACUnit.defaultUnit];
- [subject sendCompleted];
- expect([command.executing first]).toEventually(equal(@NO));
- expect(@(receivedEvent)).to(beFalsy());
- });
- qck_it(@"should send errors on the main thread", ^{
- RACCommand<RACSignal *> *command = [[RACCommand alloc] initWithSignalBlock:^(RACSignal *signal) {
- return signal;
- }];
- NSError *error = [NSError errorWithDomain:@"" code:1 userInfo:nil];
- __block RACScheduler *receivedScheduler = nil;
- [command.errors subscribeNext:^(NSError *e) {
- expect(e).to(equal(error));
- receivedScheduler = RACScheduler.currentScheduler;
- }];
- RACSignal *errorSignal = [RACSignal error:error];
- [[RACScheduler scheduler] schedule:^{
- [command execute:errorSignal];
- }];
- expect(receivedScheduler).to(beNil());
- expect(receivedScheduler).toEventually(equal(RACScheduler.mainThreadScheduler));
- });
- qck_describe(@"enabled signal", ^{
- __block RACSubject *enabledSubject;
- __block RACCommand *command;
- qck_beforeEach(^{
- enabledSubject = [RACSubject subject];
- command = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:^(id _) {
- return [RACSignal return:RACUnit.defaultUnit];
- }];
- });
- qck_it(@"should send YES by default", ^{
- expect([command.enabled first]).to(equal(@YES));
- });
- qck_it(@"should send whatever the enabledSignal has sent most recently", ^{
- [enabledSubject sendNext:@NO];
- expect([command.enabled first]).toEventually(equal(@NO));
- [enabledSubject sendNext:@YES];
- expect([command.enabled first]).toEventually(equal(@YES));
- [enabledSubject sendNext:@NO];
- expect([command.enabled first]).toEventually(equal(@NO));
- });
-
- qck_it(@"should sample enabledSignal synchronously at initialization time", ^{
- RACCommand *command = [[RACCommand alloc] initWithEnabled:[RACSignal return:@NO] signalBlock:^(id _) {
- return [RACSignal empty];
- }];
- expect([command.enabled first]).to(equal(@NO));
- });
- qck_it(@"should send NO while executing is YES and allowsConcurrentExecution is NO", ^{
- [[command.executionSignals flatten] subscribeNext:^(id _) {
- expect([command.executing first]).to(equal(@YES));
- expect([command.enabled first]).to(equal(@NO));
- }];
- expect([command.enabled first]).to(equal(@YES));
- expect(@([[command execute:nil] asynchronouslyWaitUntilCompleted:NULL])).to(beTruthy());
- expect([command.enabled first]).to(equal(@YES));
- });
- qck_it(@"should send YES while executing is YES and allowsConcurrentExecution is YES", ^{
- command.allowsConcurrentExecution = YES;
- __block BOOL outerExecuted = NO;
- __block BOOL innerExecuted = NO;
- // Prevent infinite recursion by only responding to the first value.
- [[[command.executionSignals
- take:1]
- flatten]
- subscribeNext:^(id _) {
- outerExecuted = YES;
- expect([command.executing first]).to(equal(@YES));
- expect([command.enabled first]).to(equal(@YES));
- [[command execute:nil] subscribeCompleted:^{
- innerExecuted = YES;
- }];
- }];
- expect([command.enabled first]).to(equal(@YES));
- expect([command execute:nil]).notTo(beNil());
- expect(@(outerExecuted)).toEventually(beTruthy());
- expect(@(innerExecuted)).toEventually(beTruthy());
- expect([command.enabled first]).to(equal(@YES));
- });
- qck_it(@"should send an error from -execute: when NO", ^{
- [enabledSubject sendNext:@NO];
- RACSignal *signal = [command execute:nil];
- expect(signal).notTo(beNil());
-
- __block BOOL success = NO;
- __block NSError *error = nil;
- expect([signal firstOrDefault:nil success:&success error:&error]).to(beNil());
- expect(@(success)).to(beFalsy());
- expect(error).notTo(beNil());
- expect(error.domain).to(equal(RACCommandErrorDomain));
- expect(@(error.code)).to(equal(@(RACCommandErrorNotEnabled)));
- expect(error.userInfo[RACUnderlyingCommandErrorKey]).to(beIdenticalTo(command));
- });
- qck_it(@"should always update on the main thread", ^{
- __block RACScheduler *updatedScheduler = nil;
- [[command.enabled skip:1] subscribeNext:^(id _) {
- updatedScheduler = RACScheduler.currentScheduler;
- }];
- [[RACScheduler scheduler] schedule:^{
- [enabledSubject sendNext:@NO];
- }];
- expect([command.enabled first]).to(equal(@YES));
- expect([command.enabled first]).toEventually(equal(@NO));
- expect(updatedScheduler).to(equal(RACScheduler.mainThreadScheduler));
- });
- qck_it(@"should complete when the command is deallocated even if the input signal hasn't", ^{
- __block BOOL deallocated = NO;
- __block BOOL completed = NO;
- @autoreleasepool {
- RACCommand *command __attribute__((objc_precise_lifetime)) = [[RACCommand alloc] initWithEnabled:enabledSubject signalBlock:emptySignalBlock];
- [command.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
- deallocated = YES;
- }]];
- [command.enabled subscribeCompleted:^{
- completed = YES;
- }];
- }
- expect(@(deallocated)).toEventually(beTruthy());
- expect(@(completed)).toEventually(beTruthy());
- });
- });
- QuickSpecEnd
|