NSObjectRACSelectorSignalSpec.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. //
  2. // NSObjectRACSelectorSignalSpec.m
  3. // ReactiveCocoa
  4. //
  5. // Created by Josh Abernathy on 3/18/13.
  6. // Copyright (c) 2013 GitHub, Inc. All rights reserved.
  7. //
  8. #import <Quick/Quick.h>
  9. #import <Nimble/Nimble.h>
  10. #import "RACTestObject.h"
  11. #import "RACSubclassObject.h"
  12. #import "NSObject+RACDeallocating.h"
  13. #import "NSObject+RACPropertySubscribing.h"
  14. #import "NSObject+RACSelectorSignal.h"
  15. #import "RACCompoundDisposable.h"
  16. #import "RACDisposable.h"
  17. #import "RACMulticastConnection.h"
  18. #import "RACSignal+Operations.h"
  19. #import "RACSignal.h"
  20. #import "RACTuple.h"
  21. @protocol TestProtocol
  22. @required
  23. - (BOOL)requiredMethod:(NSUInteger)number;
  24. - (void)lifeIsGood:(id)sender;
  25. @optional
  26. - (NSUInteger)optionalMethodWithObject:(id)object flag:(BOOL)flag;
  27. - (id)objectValue;
  28. @end
  29. QuickSpecBegin(NSObjectRACSelectorSignalSpec)
  30. qck_describe(@"RACTestObject", ^{
  31. qck_it(@"should send the argument for each invocation", ^{
  32. RACTestObject *object = [[RACTestObject alloc] init];
  33. __block id value;
  34. [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) {
  35. value = x.first;
  36. }];
  37. [object lifeIsGood:@42];
  38. expect(value).to(equal(@42));
  39. });
  40. qck_it(@"should send completed on deallocation", ^{
  41. __block BOOL completed = NO;
  42. __block BOOL deallocated = NO;
  43. @autoreleasepool {
  44. RACTestObject *object __attribute__((objc_precise_lifetime)) = [[RACTestObject alloc] init];
  45. [object.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
  46. deallocated = YES;
  47. }]];
  48. [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeCompleted:^{
  49. completed = YES;
  50. }];
  51. expect(@(deallocated)).to(beFalsy());
  52. expect(@(completed)).to(beFalsy());
  53. }
  54. expect(@(deallocated)).to(beTruthy());
  55. expect(@(completed)).to(beTruthy());
  56. });
  57. qck_it(@"should send for a zero-argument method", ^{
  58. RACTestObject *object = [[RACTestObject alloc] init];
  59. __block RACTuple *value;
  60. [[object rac_signalForSelector:@selector(objectValue)] subscribeNext:^(RACTuple *x) {
  61. value = x;
  62. }];
  63. (void)[object objectValue];
  64. expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]]));
  65. });
  66. qck_it(@"should send the argument for each invocation to the instance's own signal", ^{
  67. RACTestObject *object1 = [[RACTestObject alloc] init];
  68. __block id value1;
  69. [[object1 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) {
  70. value1 = x.first;
  71. }];
  72. RACTestObject *object2 = [[RACTestObject alloc] init];
  73. __block id value2;
  74. [[object2 rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) {
  75. value2 = x.first;
  76. }];
  77. [object1 lifeIsGood:@42];
  78. [object2 lifeIsGood:@"Carpe diem"];
  79. expect(value1).to(equal(@42));
  80. expect(value2).to(equal(@"Carpe diem"));
  81. });
  82. qck_it(@"should send multiple arguments for each invocation", ^{
  83. RACTestObject *object = [[RACTestObject alloc] init];
  84. __block id value1;
  85. __block id value2;
  86. [[object rac_signalForSelector:@selector(combineObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) {
  87. value1 = x.first;
  88. value2 = x.second;
  89. }];
  90. expect([object combineObjectValue:@42 andSecondObjectValue:@"foo"]).to(equal(@"42: foo"));
  91. expect(value1).to(equal(@42));
  92. expect(value2).to(equal(@"foo"));
  93. });
  94. qck_it(@"should send arguments for invocation of non-existant methods", ^{
  95. RACTestObject *object = [[RACTestObject alloc] init];
  96. __block id key;
  97. __block id value;
  98. [[object rac_signalForSelector:@selector(setObject:forKey:)] subscribeNext:^(RACTuple *x) {
  99. value = x.first;
  100. key = x.second;
  101. }];
  102. [object performSelector:@selector(setObject:forKey:) withObject:@YES withObject:@"Winner"];
  103. expect(value).to(equal(@YES));
  104. expect(key).to(equal(@"Winner"));
  105. });
  106. qck_it(@"should send arguments for invocation and invoke the original method on previously KVO'd receiver", ^{
  107. RACTestObject *object = [[RACTestObject alloc] init];
  108. [[RACObserve(object, objectValue) publish] connect];
  109. __block id key;
  110. __block id value;
  111. [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) {
  112. value = x.first;
  113. key = x.second;
  114. }];
  115. [object setObjectValue:@YES andSecondObjectValue:@"Winner"];
  116. expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy());
  117. expect(object.objectValue).to(equal(@YES));
  118. expect(object.secondObjectValue).to(equal(@"Winner"));
  119. expect(value).to(equal(@YES));
  120. expect(key).to(equal(@"Winner"));
  121. });
  122. qck_it(@"should send arguments for invocation and invoke the original method when receiver is subsequently KVO'd", ^{
  123. RACTestObject *object = [[RACTestObject alloc] init];
  124. __block id key;
  125. __block id value;
  126. [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) {
  127. value = x.first;
  128. key = x.second;
  129. }];
  130. [[RACObserve(object, objectValue) publish] connect];
  131. [object setObjectValue:@YES andSecondObjectValue:@"Winner"];
  132. expect(@(object.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy());
  133. expect(object.objectValue).to(equal(@YES));
  134. expect(object.secondObjectValue).to(equal(@"Winner"));
  135. expect(value).to(equal(@YES));
  136. expect(key).to(equal(@"Winner"));
  137. });
  138. qck_it(@"should properly implement -respondsToSelector: when called on KVO'd receiver", ^{
  139. RACTestObject *object = [[RACTestObject alloc] init];
  140. // First, setup KVO on `object`, which gives us the desired side-effect
  141. // of `object` taking on a KVO-custom subclass.
  142. [[RACObserve(object, objectValue) publish] connect];
  143. SEL selector = NSSelectorFromString(@"anyOldSelector:");
  144. // With the KVO subclass in place, call -rac_signalForSelector: to
  145. // implement -anyOldSelector: directly on the KVO subclass.
  146. [object rac_signalForSelector:selector];
  147. expect(@([object respondsToSelector:selector])).to(beTruthy());
  148. });
  149. qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd", ^{
  150. RACTestObject *object = [[RACTestObject alloc] init];
  151. SEL selector = NSSelectorFromString(@"anyOldSelector:");
  152. // Implement -anyOldSelector: on the object first
  153. [object rac_signalForSelector:selector];
  154. expect(@([object respondsToSelector:selector])).to(beTruthy());
  155. // Then KVO the object
  156. [[RACObserve(object, objectValue) publish] connect];
  157. expect(@([object respondsToSelector:selector])).to(beTruthy());
  158. });
  159. qck_it(@"should properly implement -respondsToSelector: when called on signalForSelector'd receiver that has subsequently been KVO'd, then signalForSelector'd again", ^{
  160. RACTestObject *object = [[RACTestObject alloc] init];
  161. SEL selector = NSSelectorFromString(@"anyOldSelector:");
  162. // Implement -anyOldSelector: on the object first
  163. [object rac_signalForSelector:selector];
  164. expect(@([object respondsToSelector:selector])).to(beTruthy());
  165. // Then KVO the object
  166. [[RACObserve(object, objectValue) publish] connect];
  167. expect(@([object respondsToSelector:selector])).to(beTruthy());
  168. SEL selector2 = NSSelectorFromString(@"anotherSelector:");
  169. // Then implement -anotherSelector: on the object
  170. [object rac_signalForSelector:selector2];
  171. expect(@([object respondsToSelector:selector2])).to(beTruthy());
  172. });
  173. qck_it(@"should call the right signal for two instances of the same class, adding signals for the same selector", ^{
  174. RACTestObject *object1 = [[RACTestObject alloc] init];
  175. RACTestObject *object2 = [[RACTestObject alloc] init];
  176. SEL selector = NSSelectorFromString(@"lifeIsGood:");
  177. __block id value1 = nil;
  178. [[object1 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) {
  179. value1 = x.first;
  180. }];
  181. __block id value2 = nil;
  182. [[object2 rac_signalForSelector:selector] subscribeNext:^(RACTuple *x) {
  183. value2 = x.first;
  184. }];
  185. [object1 lifeIsGood:@42];
  186. expect(value1).to(equal(@42));
  187. expect(value2).to(beNil());
  188. [object2 lifeIsGood:@420];
  189. expect(value1).to(equal(@42));
  190. expect(value2).to(equal(@420));
  191. });
  192. qck_it(@"should properly implement -respondsToSelector: for optional method from a protocol", ^{
  193. // Selector for the targeted optional method from a protocol.
  194. SEL selector = @selector(optionalProtocolMethodWithObjectValue:);
  195. RACTestObject *object1 = [[RACTestObject alloc] init];
  196. // Method implementation of the selector is added to its swizzled class.
  197. [object1 rac_signalForSelector:selector fromProtocol:@protocol(RACTestProtocol)];
  198. expect(@([object1 respondsToSelector:selector])).to(beTruthy());
  199. RACTestObject *object2 = [[RACTestObject alloc] init];
  200. // Call -rac_signalForSelector: to swizzle this instance's class,
  201. // method implementations of -respondsToSelector: and
  202. // -forwardInvocation:.
  203. [object2 rac_signalForSelector:@selector(lifeIsGood:)];
  204. // This instance should not respond to the selector because of not
  205. // calling -rac_signalForSelector: with the selector.
  206. expect(@([object2 respondsToSelector:selector])).to(beFalsy());
  207. });
  208. qck_it(@"should send non-object arguments", ^{
  209. RACTestObject *object = [[RACTestObject alloc] init];
  210. __block id value;
  211. [[object rac_signalForSelector:@selector(setIntegerValue:)] subscribeNext:^(RACTuple *x) {
  212. value = x.first;
  213. }];
  214. object.integerValue = 42;
  215. expect(value).to(equal(@42));
  216. });
  217. qck_it(@"should send on signal after the original method is invoked", ^{
  218. RACTestObject *object = [[RACTestObject alloc] init];
  219. __block BOOL invokedMethodBefore = NO;
  220. [[object rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *x) {
  221. invokedMethodBefore = object.hasInvokedSetObjectValueAndSecondObjectValue;
  222. }];
  223. [object setObjectValue:@YES andSecondObjectValue:@"Winner"];
  224. expect(@(invokedMethodBefore)).to(beTruthy());
  225. });
  226. });
  227. qck_it(@"should swizzle an NSObject method", ^{
  228. NSObject *object = [[NSObject alloc] init];
  229. __block RACTuple *value;
  230. [[object rac_signalForSelector:@selector(description)] subscribeNext:^(RACTuple *x) {
  231. value = x;
  232. }];
  233. expect([object description]).notTo(beNil());
  234. expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]]));
  235. });
  236. qck_describe(@"a class that already overrides -forwardInvocation:", ^{
  237. qck_it(@"should invoke the superclass' implementation", ^{
  238. RACSubclassObject *object = [[RACSubclassObject alloc] init];
  239. __block id value;
  240. [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) {
  241. value = x.first;
  242. }];
  243. [object lifeIsGood:@42];
  244. expect(value).to(equal(@42));
  245. expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0));
  246. [object performSelector:@selector(allObjects)];
  247. expect(value).to(equal(@42));
  248. expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects"));
  249. });
  250. qck_it(@"should not infinite recurse when KVO'd after RAC swizzled", ^{
  251. RACSubclassObject *object = [[RACSubclassObject alloc] init];
  252. __block id value;
  253. [[object rac_signalForSelector:@selector(lifeIsGood:)] subscribeNext:^(RACTuple *x) {
  254. value = x.first;
  255. }];
  256. [[RACObserve(object, objectValue) publish] connect];
  257. [object lifeIsGood:@42];
  258. expect(value).to(equal(@42));
  259. expect(@((size_t)(void*)object.forwardedSelector)).to(equal(@0));
  260. [object performSelector:@selector(allObjects)];
  261. expect(NSStringFromSelector(object.forwardedSelector)).to(equal(@"allObjects"));
  262. });
  263. });
  264. qck_describe(@"two classes in the same hierarchy", ^{
  265. __block RACTestObject *superclassObj;
  266. __block RACTuple *superclassTuple;
  267. __block RACSubclassObject *subclassObj;
  268. __block RACTuple *subclassTuple;
  269. qck_beforeEach(^{
  270. superclassObj = [[RACTestObject alloc] init];
  271. expect(superclassObj).notTo(beNil());
  272. subclassObj = [[RACSubclassObject alloc] init];
  273. expect(subclassObj).notTo(beNil());
  274. });
  275. qck_it(@"should not collide", ^{
  276. [[superclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) {
  277. superclassTuple = t;
  278. }];
  279. [[subclassObj rac_signalForSelector:@selector(combineObjectValue:andIntegerValue:)] subscribeNext:^(RACTuple *t) {
  280. subclassTuple = t;
  281. }];
  282. expect([superclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"foo: 42"));
  283. NSArray *expectedValues = @[ @"foo", @42 ];
  284. expect(superclassTuple.allObjects).to(equal(expectedValues));
  285. expect([subclassObj combineObjectValue:@"foo" andIntegerValue:42]).to(equal(@"fooSUBCLASS: 42"));
  286. expectedValues = @[ @"foo", @42 ];
  287. expect(subclassTuple.allObjects).to(equal(expectedValues));
  288. });
  289. qck_it(@"should not collide when the superclass is invoked asynchronously", ^{
  290. [[superclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) {
  291. superclassTuple = t;
  292. }];
  293. [[subclassObj rac_signalForSelector:@selector(setObjectValue:andSecondObjectValue:)] subscribeNext:^(RACTuple *t) {
  294. subclassTuple = t;
  295. }];
  296. [superclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"];
  297. expect(@(superclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beTruthy());
  298. NSArray *expectedValues = @[ @"foo", @"42" ];
  299. expect(superclassTuple.allObjects).to(equal(expectedValues));
  300. [subclassObj setObjectValue:@"foo" andSecondObjectValue:@"42"];
  301. expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).to(beFalsy());
  302. expect(@(subclassObj.hasInvokedSetObjectValueAndSecondObjectValue)).toEventually(beTruthy());
  303. expectedValues = @[ @"foo", @"42" ];
  304. expect(subclassTuple.allObjects).to(equal(expectedValues));
  305. });
  306. });
  307. qck_describe(@"-rac_signalForSelector:fromProtocol", ^{
  308. __block RACTestObject<TestProtocol> *object;
  309. __block Protocol *protocol;
  310. qck_beforeEach(^{
  311. object = (id)[[RACTestObject alloc] init];
  312. expect(object).notTo(beNil());
  313. protocol = @protocol(TestProtocol);
  314. expect(protocol).notTo(beNil());
  315. });
  316. qck_it(@"should not clobber a required method already implemented", ^{
  317. __block id value;
  318. [[object rac_signalForSelector:@selector(lifeIsGood:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) {
  319. value = x.first;
  320. }];
  321. [object lifeIsGood:@42];
  322. expect(value).to(equal(@42));
  323. });
  324. qck_it(@"should not clobber an optional method already implemented", ^{
  325. object.objectValue = @"foo";
  326. __block id value;
  327. [[object rac_signalForSelector:@selector(objectValue) fromProtocol:protocol] subscribeNext:^(RACTuple *x) {
  328. value = x;
  329. }];
  330. expect([object objectValue]).to(equal(@"foo"));
  331. expect(value).to(equal([RACTuple tupleWithObjectsFromArray:@[]]));
  332. });
  333. qck_it(@"should inject a required method", ^{
  334. __block id value;
  335. [[object rac_signalForSelector:@selector(requiredMethod:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) {
  336. value = x.first;
  337. }];
  338. expect(@([object requiredMethod:42])).to(beFalsy());
  339. expect(value).to(equal(@42));
  340. });
  341. qck_it(@"should inject an optional method", ^{
  342. __block id value;
  343. [[object rac_signalForSelector:@selector(optionalMethodWithObject:flag:) fromProtocol:protocol] subscribeNext:^(RACTuple *x) {
  344. value = x;
  345. }];
  346. expect(@([object optionalMethodWithObject:@"foo" flag:YES])).to(equal(@0));
  347. expect(value).to(equal(RACTuplePack(@"foo", @YES)));
  348. });
  349. });
  350. qck_describe(@"class reporting", ^{
  351. __block RACTestObject *object;
  352. __block Class originalClass;
  353. qck_beforeEach(^{
  354. object = [[RACTestObject alloc] init];
  355. originalClass = object.class;
  356. });
  357. qck_it(@"should report the original class", ^{
  358. [object rac_signalForSelector:@selector(lifeIsGood:)];
  359. expect(object.class).to(beIdenticalTo(originalClass));
  360. });
  361. qck_it(@"should report the original class when it's KVO'd after dynamically subclassing", ^{
  362. [object rac_signalForSelector:@selector(lifeIsGood:)];
  363. [[RACObserve(object, objectValue) publish] connect];
  364. expect(object.class).to(beIdenticalTo(originalClass));
  365. });
  366. qck_it(@"should report the original class when it's KVO'd before dynamically subclassing", ^{
  367. [[RACObserve(object, objectValue) publish] connect];
  368. [object rac_signalForSelector:@selector(lifeIsGood:)];
  369. expect(object.class).to(beIdenticalTo(originalClass));
  370. });
  371. });
  372. QuickSpecEnd