Merge pull request #197 from realm/sk-react-native-0.18

Update React Tests to support React Native 0.18
This commit is contained in:
Scott Kyle 2016-01-19 15:12:51 -08:00
commit 1bb335c587
8 changed files with 179 additions and 94 deletions

View File

@ -6,7 +6,7 @@
"start": "react-native start" "start": "react-native start"
}, },
"dependencies": { "dependencies": {
"react-native": "0.16.0", "react-native": "0.18.0",
"realm": "file:../.." "realm": "file:../.."
} }
} }

View File

@ -5,8 +5,21 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h> #import <JavaScriptCore/JavaScriptCore.h>
extern JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create); #ifdef __cplusplus
extern "C" {
#endif
typedef void (^RealmReactEventHandler)(id message);
JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create);
@interface RealmReact : NSObject @interface RealmReact : NSObject
- (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler;
- (void)removeListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler;
@end @end
#ifdef __cplusplus
}
#endif

View File

@ -2,20 +2,29 @@
* Proprietary and Confidential * Proprietary and Confidential
*/ */
extern "C" {
#import "RealmReact.h" #import "RealmReact.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import <RealmJS/js_init.h> #import "js_init.h"
#import "shared_realm.hpp"
#import <objc/runtime.h> #import <objc/runtime.h>
#import <dlfcn.h> #import <dlfcn.h>
#if DEBUG
#import <GCDWebServer/Core/GCDWebServer.h>
#import <GCDWebServer/Requests/GCDWebServerDataRequest.h>
#import <GCDWebServer/Responses/GCDWebServerDataResponse.h>
#import <GCDWebServer/Responses/GCDWebServerErrorResponse.h>
#import "rpc.hpp"
#endif
@interface NSObject () @interface NSObject ()
- (instancetype)initWithJSContext:(void *)context; - (instancetype)initWithJSContext:(void *)context;
- (JSGlobalContextRef)ctx; - (JSGlobalContextRef)ctx;
@end @end
JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) { extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) {
Ivar contextIvar = class_getInstanceVariable([executor class], "_context"); Ivar contextIvar = class_getInstanceVariable([executor class], "_context");
if (!contextIvar) { if (!contextIvar) {
return NULL; return NULL;
@ -42,34 +51,20 @@ JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool cre
return [rctJSContext ctx]; return [rctJSContext ctx];
} }
}
#import "shared_realm.hpp" @interface RealmReact () <RCTBridgeModule>
#if DEBUG
#import <GCDWebServer/Core/GCDWebServer.h>
#import <GCDWebServer/Requests/GCDWebServerDataRequest.h>
#import <GCDWebServer/Responses/GCDWebServerDataResponse.h>
#import <GCDWebServer/Responses/GCDWebServerErrorResponse.h>
#import <RealmJS/rpc.hpp>
@interface RealmReact () {
GCDWebServer *_webServer;
std::unique_ptr<realm_js::RPCServer> _rpcServer;
}
@end @end
#endif
@interface RealmReact () <RCTBridgeModule> { @implementation RealmReact {
NSMutableDictionary *_eventHandlers;
__weak NSThread *_currentJSThread; __weak NSThread *_currentJSThread;
__weak NSRunLoop *_currentJSRunLoop; __weak NSRunLoop *_currentJSRunLoop;
#if DEBUG
GCDWebServer *_webServer;
std::unique_ptr<realm_js::RPCServer> _rpcServer;
#endif
} }
@end
static __weak RealmReact *s_currentRealmModule = nil;
@implementation RealmReact
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@ -88,6 +83,37 @@ static __weak RealmReact *s_currentRealmModule = nil;
return @"Realm"; return @"Realm";
} }
- (instancetype)init {
self = [super init];
if (self) {
_eventHandlers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}
- (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler {
NSMutableOrderedSet *handlers = _eventHandlers[eventName];
if (!handlers) {
handlers = _eventHandlers[eventName] = [[NSMutableOrderedSet alloc] init];
}
[handlers addObject:handler];
}
- (void)removeListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler {
NSMutableOrderedSet *handlers = _eventHandlers[eventName];
[handlers removeObject:handler];
}
RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) {
for (RealmReactEventHandler handler in [_eventHandlers[eventName] copy]) {
handler(object);
}
}
#if DEBUG #if DEBUG
- (void)startRPC { - (void)startRPC {
[GCDWebServer setLogLevel:3]; [GCDWebServer setLogLevel:3];
@ -139,11 +165,9 @@ static __weak RealmReact *s_currentRealmModule = nil;
_webServer = nil; _webServer = nil;
_rpcServer.reset(); _rpcServer.reset();
} }
#endif #endif
- (void)shutdown { - (void)invalidate {
#if DEBUG #if DEBUG
// shutdown rpc if in chrome debug mode // shutdown rpc if in chrome debug mode
[self shutdownRPC]; [self shutdownRPC];
@ -152,7 +176,7 @@ static __weak RealmReact *s_currentRealmModule = nil;
// block until JS thread exits // block until JS thread exits
NSRunLoop *runLoop = _currentJSRunLoop; NSRunLoop *runLoop = _currentJSRunLoop;
if (runLoop) { if (runLoop) {
CFRunLoopStop([_currentJSRunLoop getCFRunLoop]); CFRunLoopStop([runLoop getCFRunLoop]);
while (_currentJSThread && !_currentJSThread.finished) { while (_currentJSThread && !_currentJSThread.finished) {
[NSThread sleepForTimeInterval:0.01]; [NSThread sleepForTimeInterval:0.01];
} }
@ -162,18 +186,18 @@ static __weak RealmReact *s_currentRealmModule = nil;
} }
- (void)dealloc { - (void)dealloc {
[self shutdown]; [self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
} }
- (void)setBridge:(RCTBridge *)bridge { - (void)setBridge:(RCTBridge *)bridge {
_bridge = bridge; _bridge = bridge;
// shutdown the last instance of this module static __weak RealmReact *s_currentModule = nil;
[s_currentRealmModule shutdown]; [s_currentModule invalidate];
s_currentRealmModule = self; s_currentModule = self;
id<RCTJavaScriptExecutor> executor = [bridge valueForKey:@"javaScriptExecutor"];
Ivar executorIvar = class_getInstanceVariable([bridge class], "_javaScriptExecutor");
id executor = object_getIvar(bridge, executorIvar);
if ([executor isKindOfClass:NSClassFromString(@"RCTWebSocketExecutor")]) { if ([executor isKindOfClass:NSClassFromString(@"RCTWebSocketExecutor")]) {
#if DEBUG #if DEBUG
[self startRPC]; [self startRPC];
@ -183,13 +207,16 @@ static __weak RealmReact *s_currentRealmModule = nil;
} }
else { else {
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
[executor executeBlockOnJavaScriptQueue:^{ [executor executeBlockOnJavaScriptQueue:^{
RealmReact *self = weakSelf; __typeof__(self) self = weakSelf;
if (!self) { if (!self) {
return; return;
} }
self->_currentJSThread = [NSThread currentThread]; self->_currentJSThread = [NSThread currentThread];
self->_currentJSRunLoop = [NSRunLoop currentRunLoop]; self->_currentJSRunLoop = [NSRunLoop currentRunLoop];
JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(executor, true); JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(executor, true);
RJSInitializeInContext(ctx); RJSInitializeInContext(ctx);
}]; }];

View File

@ -0,0 +1,33 @@
{
"env": {
"commonjs": true,
"es6": true
},
"ecmaFeatures": {
"jsx": true
},
"globals": {
"cancelAnimationFrame": false,
"clearImmediate": false,
"clearInterval": false,
"clearTimeout": false,
"console": false,
"global": false,
"requestAnimationFrame": false,
"setImmediate": false,
"setInterval": false,
"setTimeout": false
},
"plugins": [
"react"
],
"rules": {
"no-console": 0,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-undef": 2,
"react/jsx-uses-react": 2,
"react/no-direct-mutation-state": 1,
"react/prefer-es6-class": 1,
"react/react-in-jsx-scope": 2
}
}

View File

@ -4,18 +4,37 @@
'use strict'; 'use strict';
var React = require('react-native'); const React = require('react-native');
var Realm = require('realm'); const Realm = require('realm');
var RealmTests = require('realm-tests'); const RealmTests = require('realm-tests');
var { const {
AppRegistry, AppRegistry,
NativeAppEventEmitter,
NativeModules,
StyleSheet, StyleSheet,
Text, Text,
TouchableHighlight, TouchableHighlight,
View, View,
} = React; } = React;
// Listen for event to run a particular test.
NativeAppEventEmitter.addListener('realm-run-test', (test) => {
let error;
try {
RealmTests.runTest(test.suite, test.name);
} catch (e) {
error = '' + e;
}
NativeModules.Realm.emit('realm-test-finished', error);
});
// Inform the native test harness about the test suite once it's ready.
setTimeout(() => {
NativeModules.Realm.emit('realm-test-names', RealmTests.getTestNames());
}, 0);
function runTests() { function runTests() {
let testNames = RealmTests.getTestNames(); let testNames = RealmTests.getTestNames();
@ -46,7 +65,7 @@ function runTests() {
} }
} }
var ReactTests = React.createClass({ class ReactTests extends React.Component {
render() { render() {
return ( return (
<View style={styles.container}> <View style={styles.container}>
@ -60,7 +79,7 @@ var ReactTests = React.createClass({
</View> </View>
); );
} }
}); }
var styles = StyleSheet.create({ var styles = StyleSheet.create({
container: { container: {

View File

@ -18,7 +18,7 @@ static NSString * const RCTDevMenuKey = @"RCTDevMenu";
NSMutableDictionary *settings = [([defaults dictionaryForKey:RCTDevMenuKey] ?: @{}) mutableCopy]; NSMutableDictionary *settings = [([defaults dictionaryForKey:RCTDevMenuKey] ?: @{}) mutableCopy];
NSMutableDictionary *domain = [[defaults volatileDomainForName:NSArgumentDomain] mutableCopy]; NSMutableDictionary *domain = [[defaults volatileDomainForName:NSArgumentDomain] mutableCopy];
settings[@"executorClass"] = [defaults boolForKey:RealmReactEnableChromeDebuggingKey] ? @"RCTWebSocketExecutor" : @"RCTContextExecutor"; settings[@"executorClass"] = [defaults boolForKey:RealmReactEnableChromeDebuggingKey] ? @"RCTWebSocketExecutor" : @"RCTJSCExecutor";
domain[RCTDevMenuKey] = settings; domain[RCTDevMenuKey] = settings;
// Re-register the arguments domain (highest precedent and volatile) with our new overridden settings. // Re-register the arguments domain (highest precedent and volatile) with our new overridden settings.

View File

@ -6,12 +6,18 @@
#import "RCTJavaScriptExecutor.h" #import "RCTJavaScriptExecutor.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTDevMenu.h" #import "RCTDevMenu.h"
#import "RCTEventDispatcher.h"
@import RealmReact; @import RealmReact;
extern void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack); extern void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack);
extern NSMutableArray *RCTGetModuleClasses(void); extern NSMutableArray *RCTGetModuleClasses(void);
@interface RCTBridge ()
+ (instancetype)currentBridge;
- (void)setUp;
@end
@interface RealmReactTests : RealmJSTests @interface RealmReactTests : RealmJSTests
@end @end
@ -26,41 +32,40 @@ extern NSMutableArray *RCTGetModuleClasses(void);
} }
+ (Class)executorClass { + (Class)executorClass {
return NSClassFromString(@"RCTContextExecutor"); return NSClassFromString(@"RCTJSCExecutor");
} }
+ (NSString *)classNameSuffix { + (NSString *)classNameSuffix {
return @""; return @"";
} }
+ (id<RCTJavaScriptExecutor>)currentExecutor { + (RCTBridge *)currentBridge {
Class executorClass = [self executorClass]; Class executorClass = [self executorClass];
if (!executorClass) { if (!executorClass) {
return nil; return nil;
} }
static RCTBridge *s_bridge; RCTBridge *bridge = [RCTBridge currentBridge];
if (!s_bridge) { if (!bridge.valid) {
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; [self waitForNotification:RCTJavaScriptDidLoadNotification];
bridge = [RCTBridge currentBridge];
} }
if (!s_bridge.valid) { if (bridge.executorClass != executorClass) {
NSNotification *notification = [self waitForNotification:RCTJavaScriptDidLoadNotification]; bridge.executorClass = executorClass;
s_bridge = notification.userInfo[@"bridge"];
assert(s_bridge); RCTBridge *parentBridge = [bridge valueForKey:@"parentBridge"];
[parentBridge invalidate];
[parentBridge setUp];
return [self currentBridge];
} }
if (s_bridge.executorClass != executorClass) { return bridge;
s_bridge.executorClass = executorClass; }
[s_bridge reload];
// The [RCTBridge reload] method does a dispatch_async that we must run before trying again. + (id<RCTJavaScriptExecutor>)currentExecutor {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; return [[self currentBridge] valueForKey:@"javaScriptExecutor"];
return [self currentExecutor];
}
return [s_bridge valueForKey:@"javaScriptExecutor"];
} }
+ (XCTestSuite *)defaultTestSuite { + (XCTestSuite *)defaultTestSuite {
@ -78,11 +83,9 @@ extern NSMutableArray *RCTGetModuleClasses(void);
JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(ctx, false); JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(ctx, false);
} }
NSError *error; NSDictionary *testCaseNames = [self waitForEvent:@"realm-test-names"];
NSDictionary *testCaseNames = [self invokeMethod:@"getTestNames" arguments:nil error:&error]; if (!testCaseNames.count) {
NSLog(@"ERROR: No test names were provided by the JS");
if (error || !testCaseNames.count) {
NSLog(@"Error from calling getTestNames() - %@", error ?: @"None returned");
exit(1); exit(1);
} }
@ -128,34 +131,24 @@ extern NSMutableArray *RCTGetModuleClasses(void);
} }
} }
+ (id)invokeMethod:(NSString *)method arguments:(NSArray *)arguments error:(NSError * __strong *)outError { + (id)waitForEvent:(NSString *)eventName {
id<RCTJavaScriptExecutor> executor = [self currentExecutor]; __weak RealmReact *realmModule = [[self currentBridge] moduleForClass:[RealmReact class]];
assert(realmModule);
__block BOOL condition = NO; __block BOOL condition = NO;
__block id result; __block id result;
__block RealmReactEventHandler handler;
[executor executeJSCall:@"realm-tests/index.js" method:method arguments:(arguments ?: @[]) callback:^(id json, NSError *error) { __block __weak RealmReactEventHandler weakHandler = handler = ^(id object) {
// The React Native debuggerWorker.js very bizarrely returns an array five empty arrays to signify an error. [realmModule removeListenerForEvent:eventName handler:weakHandler];
if ([json isKindOfClass:[NSArray class]] && [json isEqualToArray:@[@[], @[], @[], @[], @[]]]) {
json = nil;
if (!error) { condition = YES;
error = [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: @"unknown JS error"}]; result = object;
} };
}
dispatch_async(dispatch_get_main_queue(), ^{ [realmModule addListenerForEvent:eventName handler:handler];
condition = YES;
result = json;
if (error && outError) {
*outError = error;
}
});
}];
[self waitForCondition:&condition]; [self waitForCondition:&condition];
return result; return result;
} }
@ -167,12 +160,12 @@ extern NSMutableArray *RCTGetModuleClasses(void);
module = [module substringToIndex:(module.length - suffix.length)]; module = [module substringToIndex:(module.length - suffix.length)];
} }
NSError *error; RCTBridge *bridge = [self.class currentBridge];
[self.class invokeMethod:@"runTest" arguments:@[module, method] error:&error]; [bridge.eventDispatcher sendAppEventWithName:@"realm-run-test" body:@{@"suite": module, @"name": method}];
id error = [self.class waitForEvent:@"realm-test-finished"];
if (error) { if (error) {
// TODO: Parse and use localizedFailureReason info once we can source map the failure location in JS. [self recordFailureWithDescription:[error description] inFile:@(__FILE__) atLine:__LINE__ expected:YES];
[self recordFailureWithDescription:error.localizedDescription inFile:@(__FILE__) atLine:__LINE__ expected:YES];
} }
} }

View File

@ -6,7 +6,7 @@
"start": "react-native start" "start": "react-native start"
}, },
"dependencies": { "dependencies": {
"react-native": "0.16.0", "react-native": "0.18.0",
"realm": "file:../..", "realm": "file:../..",
"realm-tests": "file:../lib" "realm-tests": "file:../lib"
} }