diff --git a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj index 5920f141..3b2df847 100644 --- a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj +++ b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj @@ -14,7 +14,7 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* RealmReactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* RealmReactTests.m */; }; - 02409E1E1BCF1F2E005F3B3E /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 02409E1B1BCF1F2E005F3B3E /* RealmJSTests.mm */; settings = {ASSET_TAGS = (); }; }; + 02409E1E1BCF1F2E005F3B3E /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 02409E1B1BCF1F2E005F3B3E /* RealmJSTests.mm */; }; 0277991C1BBF3BC600C96559 /* RealmReact.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0277991B1BBF3BB700C96559 /* RealmReact.framework */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; @@ -167,7 +167,7 @@ 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 00E356EE1AD99517003FC87E /* RealmReactTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmReactTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* RealmReactTests.m */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.objc; path = RealmReactTests.m; sourceTree = ""; tabWidth = 4; }; + 00E356F21AD99517003FC87E /* RealmReactTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RealmReactTests.m; sourceTree = ""; }; 02409E1A1BCF1F2E005F3B3E /* RealmJSTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RealmJSTests.h; path = ../../../RealmJSTests.h; sourceTree = ""; }; 02409E1B1BCF1F2E005F3B3E /* RealmJSTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RealmJSTests.mm; path = ../../../RealmJSTests.mm; sourceTree = ""; }; 027799061BBF3BB700C96559 /* RealmJS.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RealmJS.xcodeproj; path = ../../../RealmJS.xcodeproj; sourceTree = ""; }; @@ -367,9 +367,7 @@ 00E356EF1AD99517003FC87E /* RealmReactTests */, 83CBBA001A601CBA00E9B192 /* Products */, ); - indentWidth = 2; sourceTree = ""; - tabWidth = 2; }; 83CBBA001A601CBA00E9B192 /* Products */ = { isa = PBXGroup; diff --git a/tests/ReactTests/ios/ReactTests/AppDelegate.h b/tests/ReactTests/ios/ReactTests/AppDelegate.h index a9654d5e..05b4865f 100644 --- a/tests/ReactTests/ios/ReactTests/AppDelegate.h +++ b/tests/ReactTests/ios/ReactTests/AppDelegate.h @@ -1,11 +1,20 @@ -/** - * 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. - */ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// #import diff --git a/tests/ReactTests/ios/ReactTests/AppDelegate.m b/tests/ReactTests/ios/ReactTests/AppDelegate.m index e749c7b7..bf4e4fc5 100644 --- a/tests/ReactTests/ios/ReactTests/AppDelegate.m +++ b/tests/ReactTests/ios/ReactTests/AppDelegate.m @@ -1,61 +1,68 @@ -/** - * 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. - */ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// #import "AppDelegate.h" - #import "RCTRootView.h" @implementation AppDelegate -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - NSURL *jsCodeLocation; +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSURL *jsCodeLocation; - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ + /** + * Loading JavaScript code - uncomment the one you want. + * + * OPTION 1 + * Load from development server. Start the server from the repository root: + * + * $ npm start + * + * To run on device, change `localhost` to the IP address of your computer + * (you can get this by typing `ifconfig` into the terminal and selecting the + * `inet` value under `en0:`) and make sure your computer and iOS device are + * on the same Wi-Fi network. + */ - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"]; + jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"]; - /** - * OPTION 2 - * Load from pre-bundled file on disk. To re-generate the static bundle - * from the root of your project directory, run - * - * $ react-native bundle --minify - * - * see http://facebook.github.io/react-native/docs/runningondevice.html - */ + /** + * OPTION 2 + * Load from pre-bundled file on disk. To re-generate the static bundle + * from the root of your project directory, run + * + * $ react-native bundle --minify + * + * see http://facebook.github.io/react-native/docs/runningondevice.html + */ -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; + // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"ReactTests" - initialProperties:nil - launchOptions:launchOptions]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation + moduleName:@"ReactTests" + initialProperties:nil + launchOptions:launchOptions]; - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [[UIViewController alloc] init]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - return YES; + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [[UIViewController alloc] init]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; } @end diff --git a/tests/ReactTests/ios/ReactTestsTests/RealmReactTests.m b/tests/ReactTests/ios/ReactTestsTests/RealmReactTests.m index 3b712f24..5962ad9f 100644 --- a/tests/ReactTests/ios/ReactTestsTests/RealmReactTests.m +++ b/tests/ReactTests/ios/ReactTestsTests/RealmReactTests.m @@ -19,32 +19,71 @@ #import "RealmJSTests.h" #import "Base/RCTJavaScriptExecutor.h" #import "Base/RCTBridge.h" +#import "Modules/RCTDevMenu.h" +@import ObjectiveC; @import RealmReact; extern void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack); -static id s_currentJavaScriptExecutor; - @interface RealmReactTests : RealmJSTests @end +@interface RealmReactChromeTests : RealmReactTests +@end + @implementation RealmReactTests -+ (XCTestSuite *)defaultTestSuite { - NSNotification *notification = [self waitForNotification:RCTJavaScriptDidLoadNotification]; - RCTBridge *bridge = notification.userInfo[@"bridge"]; ++ (void)load { + // Swap the [RCTDevMenu init] method with [NSObject init] in order to disable RCTDevMenu completely. + IMP init = class_getMethodImplementation([NSObject class], @selector(init)); + class_replaceMethod([RCTDevMenu class], @selector(init), init, NULL); +} - if (!bridge) { - NSLog(@"No RCTBridge provided by RCTJavaScriptDidLoadNotification"); ++ (Class)executorClass { + return NSClassFromString(@"RCTContextExecutor"); +} + ++ (NSString *)classNameSuffix { + return @""; +} + ++ (id)currentExecutor { + Class executorClass = [self executorClass]; + if (!executorClass) { + NSLog(@"%@: Executor class not found", self); exit(1); } - s_currentJavaScriptExecutor = [bridge valueForKey:@"javaScriptExecutor"]; - assert(s_currentJavaScriptExecutor); - + static RCTBridge *s_bridge; + if (!s_bridge.valid) { + NSNotification *notification = [self waitForNotification:RCTJavaScriptDidLoadNotification];; + s_bridge = notification.userInfo[@"bridge"]; + + if (!s_bridge) { + NSLog(@"No RCTBridge provided by RCTJavaScriptDidLoadNotification"); + exit(1); + } + } + + if (s_bridge.executorClass != executorClass) { + s_bridge.executorClass = executorClass; + [s_bridge reload]; + + // The [RCTBridge reload] method does a dispatch_async that we must run before trying again. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + return [self currentExecutor]; + } + + return [s_bridge valueForKey:@"javaScriptExecutor"]; +} + ++ (XCTestSuite *)defaultTestSuite { + id executor = [self currentExecutor]; + // FIXME: Remove this nonsense once the crashes go away when a test fails! - JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(s_currentJavaScriptExecutor, false); + JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(executor, false); if (ctx) { JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(ctx, false); } @@ -57,8 +96,16 @@ static id s_currentJavaScriptExecutor; exit(1); } - XCTestSuite *suite = [super defaultTestSuite]; + NSString *nameSuffix = [self classNameSuffix]; + if (nameSuffix.length) { + NSMutableDictionary *renamedTestCaseNames = [[NSMutableDictionary alloc] init]; + for (NSString *name in testCaseNames) { + renamedTestCaseNames[[name stringByAppendingString:nameSuffix]] = testCaseNames[name]; + } + testCaseNames = renamedTestCaseNames; + } + XCTestSuite *suite = [super defaultTestSuite]; for (XCTestSuite *testSuite in [self testSuitesFromDictionary:testCaseNames]) { [suite addTest:testSuite]; } @@ -67,50 +114,73 @@ static id s_currentJavaScriptExecutor; } + (NSNotification *)waitForNotification:(NSString *)notificationName { - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + __block BOOL condition = NO; __block NSNotification *notification; id token = [nc addObserverForName:notificationName object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { + condition = YES; notification = note; }]; - while (!notification) { + [self waitForCondition:&condition]; + [nc removeObserver:token]; + + return notification; +} + ++ (void)waitForCondition:(BOOL *)condition { + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + + while (!*condition) { @autoreleasepool { [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } } - - [nc removeObserver:token]; - return notification; } + (id)invokeMethod:(NSString *)method inModule:(NSString *)module error:(NSError * __strong *)outError { + id executor = [self currentExecutor]; module = [NSString stringWithFormat:@"realm-tests/%@.js", module]; - dispatch_group_t group = dispatch_group_create(); + __block BOOL condition = NO; __block id result; - dispatch_group_enter(group); + [executor executeJSCall:module method:method arguments:@[] callback:^(id json, NSError *error) { + // The React Native debuggerWorker.js very bizarrely returns an array five empty arrays to signify an error. + if ([json isKindOfClass:[NSArray class]] && [json isEqualToArray:@[@[], @[], @[], @[], @[]]]) { + json = nil; - [s_currentJavaScriptExecutor executeJSCall:module method:method arguments:@[] callback:^(id json, NSError *error) { - result = json; - - if (error && outError) { - *outError = error; + if (!error) { + error = [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: @"unknown JS error"}]; + } } - dispatch_group_leave(group); + dispatch_async(dispatch_get_main_queue(), ^{ + condition = YES; + result = json; + + if (error && outError) { + *outError = error; + } + }); }]; - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + [self waitForCondition:&condition]; return result; } - (void)invokeMethod:(NSString *)method { + NSString *module = NSStringFromClass(self.class); + NSString *suffix = [self.class classNameSuffix]; + + if (suffix.length && [module hasSuffix:suffix]) { + module = [module substringToIndex:(module.length - suffix.length)]; + } + NSError *error; - [self.class invokeMethod:method inModule:NSStringFromClass(self.class) error:&error]; + [self.class invokeMethod:method inModule:module error:&error]; if (error) { // TODO: Parse and use localizedFailureReason info once we can source map the failure location in JS. @@ -119,3 +189,15 @@ static id s_currentJavaScriptExecutor; } @end + +@implementation RealmReactChromeTests + ++ (Class)executorClass { + return NSClassFromString(@"RCTWebSocketExecutor"); +} + ++ (NSString *)classNameSuffix { + return @"_Chrome"; +} + +@end