RACKVOChannelSpec.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. //
  2. // RACKVOChannelSpec.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Uri Baghin on 16/12/2012.
  6. // Copyright (c) 2012 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "RACTestObject.h"
  11. #import "RACChannelExamples.h"
  12. #import "RACPropertySignalExamples.h"
  13. #import "NSObject+RACDeallocating.h"
  14. #import "NSObject+RACKVOWrapper.h"
  15. #import "RACCompoundDisposable.h"
  16. #import "RACDisposable.h"
  17. #import "RACKVOChannel.h"
  18. #import "RACSignal+Operations.h"
  19. QuickSpecBegin(RACKVOChannelSpec)
  20. qck_describe(@"RACKVOChannel", ^{
  21. __block RACTestObject *object;
  22. __block RACKVOChannel *channel;
  23. id value1 = @"test value 1";
  24. id value2 = @"test value 2";
  25. id value3 = @"test value 3";
  26. NSArray *values = @[ value1, value2, value3 ];
  27. qck_beforeEach(^{
  28. object = [[RACTestObject alloc] init];
  29. channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil];
  30. });
  31. id setupBlock = ^(RACTestObject *testObject, NSString *keyPath, id nilValue, RACSignal *signal) {
  32. RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:testObject keyPath:keyPath nilValue:nilValue];
  33. [signal subscribe:channel.followingTerminal];
  34. };
  35. qck_itBehavesLike(RACPropertySignalExamples, ^{
  36. return @{ RACPropertySignalExamplesSetupBlock: setupBlock };
  37. });
  38. qck_itBehavesLike(RACChannelExamples, ^{
  39. return @{
  40. RACChannelExampleCreateBlock: [^{
  41. return [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil];
  42. } copy]
  43. };
  44. });
  45. qck_it(@"should send the object's current value when subscribed to followingTerminal", ^{
  46. __block id receivedValue = @"received value should not be this";
  47. [[channel.followingTerminal take:1] subscribeNext:^(id x) {
  48. receivedValue = x;
  49. }];
  50. expect(receivedValue).to(beNil());
  51. object.stringValue = value1;
  52. [[channel.followingTerminal take:1] subscribeNext:^(id x) {
  53. receivedValue = x;
  54. }];
  55. expect(receivedValue).to(equal(value1));
  56. });
  57. qck_it(@"should send the object's new value on followingTerminal when it's changed", ^{
  58. object.stringValue = value1;
  59. NSMutableArray *receivedValues = [NSMutableArray array];
  60. [channel.followingTerminal subscribeNext:^(id x) {
  61. [receivedValues addObject:x];
  62. }];
  63. object.stringValue = value2;
  64. object.stringValue = value3;
  65. expect(receivedValues).to(equal(values));
  66. });
  67. qck_it(@"should set the object's value using values sent to the followingTerminal", ^{
  68. expect(object.stringValue).to(beNil());
  69. [channel.followingTerminal sendNext:value1];
  70. expect(object.stringValue).to(equal(value1));
  71. [channel.followingTerminal sendNext:value2];
  72. expect(object.stringValue).to(equal(value2));
  73. });
  74. qck_it(@"should be able to subscribe to signals", ^{
  75. NSMutableArray *receivedValues = [NSMutableArray array];
  76. [object rac_observeKeyPath:@keypath(object.stringValue) options:0 observer:self block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
  77. [receivedValues addObject:value];
  78. }];
  79. RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
  80. [subscriber sendNext:value1];
  81. [subscriber sendNext:value2];
  82. [subscriber sendNext:value3];
  83. return nil;
  84. }];
  85. [signal subscribe:channel.followingTerminal];
  86. expect(receivedValues).to(equal(values));
  87. });
  88. qck_it(@"should complete both terminals when the target deallocates", ^{
  89. __block BOOL leadingCompleted = NO;
  90. __block BOOL followingCompleted = NO;
  91. __block BOOL deallocated = NO;
  92. @autoreleasepool {
  93. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  94. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  95. deallocated = YES;
  96. }]];
  97. RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil];
  98. [channel.leadingTerminal subscribeCompleted:^{
  99. leadingCompleted = YES;
  100. }];
  101. [channel.followingTerminal subscribeCompleted:^{
  102. followingCompleted = YES;
  103. }];
  104. expect(@(deallocated)).to(beFalsy());
  105. expect(@(leadingCompleted)).to(beFalsy());
  106. expect(@(followingCompleted)).to(beFalsy());
  107. }
  108. expect(@(deallocated)).to(beTruthy());
  109. expect(@(leadingCompleted)).to(beTruthy());
  110. expect(@(followingCompleted)).to(beTruthy());
  111. });
  112. qck_it(@"should deallocate when the target deallocates", ^{
  113. __block BOOL targetDeallocated = NO;
  114. __block BOOL channelDeallocated = NO;
  115. @autoreleasepool {
  116. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  117. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  118. targetDeallocated = YES;
  119. }]];
  120. RACKVOChannel *channel = [[RACKVOChannel alloc] initWithTarget:object keyPath:@keypath(object.stringValue) nilValue:nil];
  121. [channel.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  122. channelDeallocated = YES;
  123. }]];
  124. expect(@(targetDeallocated)).to(beFalsy());
  125. expect(@(channelDeallocated)).to(beFalsy());
  126. }
  127. expect(@(targetDeallocated)).to(beTruthy());
  128. expect(@(channelDeallocated)).to(beTruthy());
  129. });
  130. });
  131. qck_describe(@"RACChannelTo", ^{
  132. __block RACTestObject *a;
  133. __block RACTestObject *b;
  134. __block RACTestObject *c;
  135. __block NSString *testName1;
  136. __block NSString *testName2;
  137. __block NSString *testName3;
  138. qck_beforeEach(^{
  139. a = [[RACTestObject alloc] init];
  140. b = [[RACTestObject alloc] init];
  141. c = [[RACTestObject alloc] init];
  142. testName1 = @"sync it!";
  143. testName2 = @"sync it again!";
  144. testName3 = @"sync it once more!";
  145. });
  146. qck_it(@"should keep objects' properties in sync", ^{
  147. RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue);
  148. expect(a.stringValue).to(beNil());
  149. expect(b.stringValue).to(beNil());
  150. a.stringValue = testName1;
  151. expect(a.stringValue).to(equal(testName1));
  152. expect(b.stringValue).to(equal(testName1));
  153. b.stringValue = testName2;
  154. expect(a.stringValue).to(equal(testName2));
  155. expect(b.stringValue).to(equal(testName2));
  156. a.stringValue = nil;
  157. expect(a.stringValue).to(beNil());
  158. expect(b.stringValue).to(beNil());
  159. });
  160. qck_it(@"should keep properties identified by keypaths in sync", ^{
  161. RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue);
  162. a.strongTestObjectValue = [[RACTestObject alloc] init];
  163. b.strongTestObjectValue = [[RACTestObject alloc] init];
  164. a.strongTestObjectValue.stringValue = testName1;
  165. expect(a.strongTestObjectValue.stringValue).to(equal(testName1));
  166. expect(b.strongTestObjectValue.stringValue).to(equal(testName1));
  167. expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue));
  168. b.strongTestObjectValue = nil;
  169. expect(a.strongTestObjectValue.stringValue).to(beNil());
  170. c.stringValue = testName2;
  171. b.strongTestObjectValue = c;
  172. expect(a.strongTestObjectValue.stringValue).to(equal(testName2));
  173. expect(b.strongTestObjectValue.stringValue).to(equal(testName2));
  174. expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue));
  175. });
  176. qck_it(@"should update properties identified by keypaths when the intermediate values change", ^{
  177. RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue);
  178. a.strongTestObjectValue = [[RACTestObject alloc] init];
  179. b.strongTestObjectValue = [[RACTestObject alloc] init];
  180. c.stringValue = testName1;
  181. b.strongTestObjectValue = c;
  182. expect(a.strongTestObjectValue.stringValue).to(equal(testName1));
  183. expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue));
  184. });
  185. qck_it(@"should update properties identified by keypaths when the channel was created when one of the two objects had an intermediate nil value", ^{
  186. RACChannelTo(a, strongTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue);
  187. b.strongTestObjectValue = [[RACTestObject alloc] init];
  188. c.stringValue = testName1;
  189. a.strongTestObjectValue = c;
  190. expect(a.strongTestObjectValue.stringValue).to(equal(testName1));
  191. expect(b.strongTestObjectValue.stringValue).to(equal(testName1));
  192. expect(a.strongTestObjectValue).notTo(equal(b.strongTestObjectValue));
  193. });
  194. qck_it(@"should take the value of the object being bound to at the start", ^{
  195. a.stringValue = testName1;
  196. b.stringValue = testName2;
  197. RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue);
  198. expect(a.stringValue).to(equal(testName2));
  199. expect(b.stringValue).to(equal(testName2));
  200. });
  201. qck_it(@"should update the value even if it's the same value the object had before it was bound", ^{
  202. a.stringValue = testName1;
  203. b.stringValue = testName2;
  204. RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue);
  205. expect(a.stringValue).to(equal(testName2));
  206. expect(b.stringValue).to(equal(testName2));
  207. b.stringValue = testName1;
  208. expect(a.stringValue).to(equal(testName1));
  209. expect(b.stringValue).to(equal(testName1));
  210. });
  211. qck_it(@"should bind transitively", ^{
  212. a.stringValue = testName1;
  213. b.stringValue = testName2;
  214. c.stringValue = testName3;
  215. RACChannelTo(a, stringValue) = RACChannelTo(b, stringValue);
  216. RACChannelTo(b, stringValue) = RACChannelTo(c, stringValue);
  217. expect(a.stringValue).to(equal(testName3));
  218. expect(b.stringValue).to(equal(testName3));
  219. expect(c.stringValue).to(equal(testName3));
  220. c.stringValue = testName1;
  221. expect(a.stringValue).to(equal(testName1));
  222. expect(b.stringValue).to(equal(testName1));
  223. expect(c.stringValue).to(equal(testName1));
  224. b.stringValue = testName2;
  225. expect(a.stringValue).to(equal(testName2));
  226. expect(b.stringValue).to(equal(testName2));
  227. expect(c.stringValue).to(equal(testName2));
  228. a.stringValue = testName3;
  229. expect(a.stringValue).to(equal(testName3));
  230. expect(b.stringValue).to(equal(testName3));
  231. expect(c.stringValue).to(equal(testName3));
  232. });
  233. qck_it(@"should bind changes made by KVC on arrays", ^{
  234. b.arrayValue = @[];
  235. RACChannelTo(a, arrayValue) = RACChannelTo(b, arrayValue);
  236. [[b mutableArrayValueForKeyPath:@keypath(b.arrayValue)] addObject:@1];
  237. expect(a.arrayValue).to(equal(b.arrayValue));
  238. });
  239. qck_it(@"should bind changes made by KVC on sets", ^{
  240. b.setValue = [NSSet set];
  241. RACChannelTo(a, setValue) = RACChannelTo(b, setValue);
  242. [[b mutableSetValueForKeyPath:@keypath(b.setValue)] addObject:@1];
  243. expect(a.setValue).to(equal(b.setValue));
  244. });
  245. qck_it(@"should bind changes made by KVC on ordered sets", ^{
  246. b.orderedSetValue = [NSOrderedSet orderedSet];
  247. RACChannelTo(a, orderedSetValue) = RACChannelTo(b, orderedSetValue);
  248. [[b mutableOrderedSetValueForKeyPath:@keypath(b.orderedSetValue)] addObject:@1];
  249. expect(a.orderedSetValue).to(equal(b.orderedSetValue));
  250. });
  251. qck_it(@"should handle deallocation of intermediate objects correctly even without support from KVO", ^{
  252. __block BOOL wasDisposed = NO;
  253. RACChannelTo(a, weakTestObjectValue.stringValue) = RACChannelTo(b, strongTestObjectValue.stringValue);
  254. b.strongTestObjectValue = [[RACTestObject alloc] init];
  255. @autoreleasepool {
  256. RACTestObject *object = [[RACTestObject alloc] init];
  257. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  258. wasDisposed = YES;
  259. }]];
  260. a.weakTestObjectValue = object;
  261. object.stringValue = testName1;
  262. expect(@(wasDisposed)).to(beFalsy());
  263. expect(b.strongTestObjectValue.stringValue).to(equal(testName1));
  264. }
  265. expect(@(wasDisposed)).toEventually(beTruthy());
  266. expect(b.strongTestObjectValue.stringValue).to(beNil());
  267. });
  268. qck_it(@"should stop binding when disposed", ^{
  269. RACChannelTerminal *aTerminal = RACChannelTo(a, stringValue);
  270. RACChannelTerminal *bTerminal = RACChannelTo(b, stringValue);
  271. a.stringValue = testName1;
  272. RACDisposable *disposable = [aTerminal subscribe:bTerminal];
  273. expect(a.stringValue).to(equal(testName1));
  274. expect(b.stringValue).to(equal(testName1));
  275. a.stringValue = testName2;
  276. expect(a.stringValue).to(equal(testName2));
  277. expect(b.stringValue).to(equal(testName2));
  278. [disposable dispose];
  279. a.stringValue = testName3;
  280. expect(a.stringValue).to(equal(testName3));
  281. expect(b.stringValue).to(equal(testName2));
  282. });
  283. qck_it(@"should use the nilValue when sent nil", ^{
  284. RACChannelTerminal *terminal = RACChannelTo(a, integerValue, @5);
  285. expect(@(a.integerValue)).to(equal(@0));
  286. [terminal sendNext:@2];
  287. expect(@(a.integerValue)).to(equal(@2));
  288. [terminal sendNext:nil];
  289. expect(@(a.integerValue)).to(equal(@5));
  290. });
  291. qck_it(@"should use the nilValue when an intermediate object is nil", ^{
  292. __block BOOL wasDisposed = NO;
  293. RACChannelTo(a, weakTestObjectValue.integerValue, @5) = RACChannelTo(b, strongTestObjectValue.integerValue, @5);
  294. b.strongTestObjectValue = [[RACTestObject alloc] init];
  295. @autoreleasepool {
  296. RACTestObject *object = [[RACTestObject alloc] init];
  297. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  298. wasDisposed = YES;
  299. }]];
  300. a.weakTestObjectValue = object;
  301. object.integerValue = 2;
  302. expect(@(wasDisposed)).to(beFalsy());
  303. expect(@(b.strongTestObjectValue.integerValue)).to(equal(@2));
  304. }
  305. expect(@(wasDisposed)).toEventually(beTruthy());
  306. expect(@(b.strongTestObjectValue.integerValue)).to(equal(@5));
  307. });
  308. });
  309. QuickSpecEnd