diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m index a3bcde2548a41b..81ccffa35b4096 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTAllocationTests.m @@ -69,13 +69,40 @@ - (void)invalidate @interface RCTAllocationTests : XCTestCase @end -@implementation RCTAllocationTests +@implementation RCTAllocationTests { + NSURL *_bundleURL; +} + +- (void)setUp +{ + [super setUp]; + + NSString *bundleContents = + @"var __fbBatchedBridge = {" + " callFunctionReturnFlushedQueue: function() {}," + " invokeCallbackAndReturnFlushedQueue: function() {}," + " flushedQueue: function() {}," + "};"; + + NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]; + [[NSFileManager defaultManager] createDirectoryAtURL:tempDir withIntermediateDirectories:YES attributes:nil error:NULL]; + + _bundleURL = [tempDir URLByAppendingPathComponent:@"rctallocationtests-bundle.js"]; + [bundleContents writeToURL:_bundleURL atomically:YES encoding:NSUTF8StringEncoding error:NULL]; +} + +- (void)tearDown +{ + [super tearDown]; + + [[NSFileManager defaultManager] removeItemAtURL:_bundleURL error:NULL]; +} - (void)testBridgeIsDeallocated { __weak RCTBridge *weakBridge; @autoreleasepool { - RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:nil + RCTRootView *view = [[RCTRootView alloc] initWithBundleURL:_bundleURL moduleName:@"" initialProperties:nil launchOptions:nil]; @@ -91,7 +118,7 @@ - (void)testModulesAreInvalidated { AllocationTestModule *module = [AllocationTestModule new]; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:^{ return @[module]; } @@ -109,11 +136,11 @@ - (void)testModulesAreDeallocated __weak AllocationTestModule *weakModule; @autoreleasepool { AllocationTestModule *module = [AllocationTestModule new]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil - moduleProvider:^{ - return @[module]; - } - launchOptions:nil]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL + moduleProvider:^{ + return @[module]; + } + launchOptions:nil]; weakModule = module; XCTAssertNotNil(weakModule, @"AllocationTestModule should have been created"); (void)bridge; @@ -140,7 +167,7 @@ - (void)testJavaScriptExecutorIsDeallocated { __weak id weakExecutor; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; weakExecutor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; @@ -156,7 +183,7 @@ - (void)testJavaScriptContextIsDeallocated { __weak id weakContext; @autoreleasepool { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"]; @@ -171,7 +198,7 @@ - (void)testJavaScriptContextIsDeallocated - (void)testContentViewIsInvalidated { - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; __weak UIView *rootContentView; @@ -190,7 +217,7 @@ - (void)testUnderlyingBridgeIsDeallocated RCTBridge *bridge; __weak id batchedBridge; @autoreleasepool { - bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:nil launchOptions:nil]; + bridge = [[RCTBridge alloc] initWithBundleURL:_bundleURL moduleProvider:nil launchOptions:nil]; batchedBridge = bridge.batchedBridge; XCTAssertTrue([batchedBridge isValid], @"RCTBatchedBridge should be valid"); [bridge reload]; diff --git a/React/Executors/RCTJSCExecutor.m b/React/Executors/RCTJSCExecutor.m index 1d52ae5c9522ea..b2d432be3883eb 100644 --- a/React/Executors/RCTJSCExecutor.m +++ b/React/Executors/RCTJSCExecutor.m @@ -119,8 +119,8 @@ @implementation RCTJSCExecutor static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) { - NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"unknown JS error"; - NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"no details"; + NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError, NULL) : @"Unknown JS error"; + NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, NULL, 2) : @"No details"; return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}]; } @@ -394,14 +394,12 @@ - (void)_executeJSCall:(NSString *)method JSStringRelease(moduleNameJSStringRef); if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) { - // get method JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method); JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef); JSStringRelease(methodNameJSStringRef); - if (methodJSRef != NULL && errorJSRef == NULL) { - + if (methodJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, methodJSRef)) { // direct method invoke with no arguments if (arguments.count == 0) { resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef); @@ -433,11 +431,22 @@ - (void)_executeJSCall:(NSString *)method JSStringRelease(argsJSStringRef); } } + } else { + if (!errorJSRef && JSValueIsUndefined(contextJSRef, methodJSRef)) { + error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to execute JS call: method %@ is undefined", method]); + } + } + } else { + if (!errorJSRef && JSValueIsUndefined(contextJSRef, moduleJSRef)) { + error = RCTErrorWithMessage(@"Unable to execute JS call: __fbBatchedBridge is undefined"); } } - if (errorJSRef) { - onComplete(nil, RCTNSErrorFromJSError(contextJSRef, errorJSRef)); + if (errorJSRef || error) { + if (!error) { + error = RCTNSErrorFromJSError(contextJSRef, errorJSRef); + } + onComplete(nil, error); return; }