/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #import #import #import #import #import #import #import #import static const NSUInteger kNameIndex = 0; static const NSUInteger kConstantsIndex = 1; static const NSUInteger kMethodsIndex = 2; @interface TestExecutor : NSObject @property (nonatomic, readonly, copy) NSMutableDictionary *injectedStuff; @end @implementation TestExecutor @synthesize valid = _valid; RCT_EXPORT_MODULE() - (void)setUp {} - (instancetype)init { if (self = [super init]) { _injectedStuff = [NSMutableDictionary dictionary]; } return self; } - (BOOL)isValid { return _valid; } - (void)flushedQueue:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } - (void)callFunctionOnModule:(__unused NSString *)module method:(__unused NSString *)method arguments:(__unused NSArray *)args callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } - (void)invokeCallbackID:(__unused NSNumber *)cbID arguments:(__unused NSArray *)args callback:(RCTJavaScriptCallback)onComplete { onComplete(nil, nil); } - (void)executeApplicationScript:(__unused NSString *)script sourceURL:(__unused NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete { onComplete(nil); } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { block(); } - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block { block(); } - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { _injectedStuff[objectName] = script; onComplete(nil); } - (void)invalidate { _valid = NO; } @end // Define a module that is not explicitly registered with RCT_EXPORT_MODULE @interface UnregisteredTestModule : NSObject @property (nonatomic, assign) BOOL testMethodCalled; @end @implementation UnregisteredTestModule @synthesize methodQueue = _methodQueue; + (NSString *)moduleName { return @"UnregisteredTestModule"; } RCT_EXPORT_METHOD(testMethod) { _testMethodCalled = YES; } @end @interface RCTBridgeTests : XCTestCase { RCTBridge *_bridge; __weak TestExecutor *_jsExecutor; BOOL _testMethodCalled; UnregisteredTestModule *_unregisteredTestModule; } @end @implementation RCTBridgeTests @synthesize methodQueue = _methodQueue; RCT_EXPORT_MODULE(TestModule) - (void)setUp { [super setUp]; _unregisteredTestModule = [UnregisteredTestModule new]; NSBundle *bundle = [NSBundle bundleForClass:[self class]]; _bridge = [[RCTBridge alloc] initWithBundleURL:[bundle URLForResource:@"RNTesterUnitTestsBundle" withExtension:@"js"] moduleProvider:^{ return @[self, self->_unregisteredTestModule]; } launchOptions:nil]; _bridge.executorClass = [TestExecutor class]; // Force to recreate the executor with the new class // - reload: doesn't work here since bridge hasn't loaded yet. [_bridge invalidate]; [_bridge setUp]; _jsExecutor = _bridge.batchedBridge.javaScriptExecutor; XCTAssertNotNil(_jsExecutor); } - (void)tearDown { [super tearDown]; _testMethodCalled = NO; [_bridge invalidate]; RCT_RUN_RUNLOOP_WHILE(_jsExecutor.isValid); _bridge = nil; } - (void)testHookRegistration { NSString *injectedStuff; RCT_RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"])); XCTAssertNotNil(injectedStuff); __block NSNumber *testModuleID = nil; __block NSDictionary *testConstants = nil; __block NSNumber *testMethodID = nil; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, BOOL *stop) { if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"TestModule"]) { testModuleID = @(i); testConstants = moduleConfig[kConstantsIndex]; testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]); *stop = YES; } }]; XCTAssertNotNil(remoteModuleConfig); XCTAssertNotNil(testModuleID); XCTAssertNotNil(testConstants); XCTAssertEqualObjects(testConstants[@"eleventyMillion"], @42); XCTAssertNotNil(testMethodID); } - (void)testCallNativeMethod { NSString *injectedStuff; RCT_RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"])); XCTAssertNotNil(injectedStuff); __block NSNumber *testModuleID = nil; __block NSNumber *testMethodID = nil; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"TestModule"]) { testModuleID = @(i); testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]); *stop = YES; } }]; XCTAssertNotNil(testModuleID); XCTAssertNotNil(testMethodID); NSArray *args = @[@1234, @5678, @"stringy", @{@"a": @1}, @42]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]]; [_bridge.batchedBridge handleBuffer:buffer batchEnded:YES]; dispatch_sync(_methodQueue, ^{ // clear the queue XCTAssertTrue(self->_testMethodCalled); }); } - (void)testCallUnregisteredModuleMethod { NSString *injectedStuff; RCT_RUN_RUNLOOP_WHILE(!(injectedStuff = _jsExecutor.injectedStuff[@"__fbBatchedBridgeConfig"])); XCTAssertNotNil(injectedStuff); __block NSNumber *testModuleID = nil; __block NSNumber *testMethodID = nil; NSArray *remoteModuleConfig = RCTJSONParse(injectedStuff, NULL)[@"remoteModuleConfig"]; [remoteModuleConfig enumerateObjectsUsingBlock:^(id moduleConfig, NSUInteger i, __unused BOOL *stop) { if ([moduleConfig isKindOfClass:[NSArray class]] && [moduleConfig[kNameIndex] isEqualToString:@"UnregisteredTestModule"]) { testModuleID = @(i); testMethodID = @([moduleConfig[kMethodsIndex] indexOfObject:@"testMethod"]); *stop = YES; } }]; XCTAssertNotNil(testModuleID); XCTAssertNotNil(testMethodID); NSArray *args = @[]; NSArray *buffer = @[@[testModuleID], @[testMethodID], @[args]]; [_bridge.batchedBridge handleBuffer:buffer batchEnded:YES]; dispatch_sync(_unregisteredTestModule.methodQueue, ^{ XCTAssertTrue(self->_unregisteredTestModule.testMethodCalled); }); } - (void)DISABLED_testBadArgumentsCount { //NSArray *bufferWithMissingArgument = @[@[@1], @[@0], @[@[@1234, @5678, @"stringy", @{@"a": @1}/*, @42*/]], @[], @1234567]; //[_bridge handleBuffer:bufferWithMissingArgument]; NSLog(@"WARNING: testBadArgumentsCount is temporarily disabled until we have a better way to test cases that we expect to trigger redbox errors"); } RCT_EXPORT_METHOD(testMethod:(NSInteger)integer number:(nonnull NSNumber *)number string:(NSString *)string dictionary:(NSDictionary *)dict callback:(RCTResponseSenderBlock)callback) { _testMethodCalled = YES; XCTAssertTrue(integer == 1234); XCTAssertEqualObjects(number, @5678); XCTAssertEqualObjects(string, @"stringy"); XCTAssertEqualObjects(dict, @{@"a": @1}); XCTAssertNotNil(callback); } - (NSDictionary *)constantsToExport { return @{@"eleventyMillion": @42}; } @end