Update React Tests to support React Native 0.18
We had to shift to using an event-based architecture to communicate with the page from native code.
This commit is contained in:
parent
381dbbe7ba
commit
d7f80e22c8
|
@ -5,8 +5,21 @@
|
|||
#import <Foundation/Foundation.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
|
||||
|
||||
- (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler;
|
||||
- (void)removeListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -2,20 +2,29 @@
|
|||
* Proprietary and Confidential
|
||||
*/
|
||||
|
||||
extern "C" {
|
||||
#import "RealmReact.h"
|
||||
#import "RCTBridge.h"
|
||||
|
||||
#import <RealmJS/js_init.h>
|
||||
#import "js_init.h"
|
||||
#import "shared_realm.hpp"
|
||||
|
||||
#import <objc/runtime.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 ()
|
||||
- (instancetype)initWithJSContext:(void *)context;
|
||||
- (JSGlobalContextRef)ctx;
|
||||
@end
|
||||
|
||||
JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) {
|
||||
extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) {
|
||||
Ivar contextIvar = class_getInstanceVariable([executor class], "_context");
|
||||
if (!contextIvar) {
|
||||
return NULL;
|
||||
|
@ -42,34 +51,20 @@ JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool cre
|
|||
|
||||
return [rctJSContext ctx];
|
||||
}
|
||||
}
|
||||
|
||||
#import "shared_realm.hpp"
|
||||
|
||||
#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;
|
||||
}
|
||||
@interface RealmReact () <RCTBridgeModule>
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface RealmReact () <RCTBridgeModule> {
|
||||
@implementation RealmReact {
|
||||
NSMutableDictionary *_eventHandlers;
|
||||
__weak NSThread *_currentJSThread;
|
||||
__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;
|
||||
|
||||
|
@ -88,6 +83,34 @@ static __weak RealmReact *s_currentRealmModule = nil;
|
|||
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] ?: (_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
|
||||
- (void)startRPC {
|
||||
[GCDWebServer setLogLevel:3];
|
||||
|
@ -139,11 +162,9 @@ static __weak RealmReact *s_currentRealmModule = nil;
|
|||
_webServer = nil;
|
||||
_rpcServer.reset();
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
- (void)shutdown {
|
||||
- (void)invalidate {
|
||||
#if DEBUG
|
||||
// shutdown rpc if in chrome debug mode
|
||||
[self shutdownRPC];
|
||||
|
@ -152,7 +173,7 @@ static __weak RealmReact *s_currentRealmModule = nil;
|
|||
// block until JS thread exits
|
||||
NSRunLoop *runLoop = _currentJSRunLoop;
|
||||
if (runLoop) {
|
||||
CFRunLoopStop([_currentJSRunLoop getCFRunLoop]);
|
||||
CFRunLoopStop([runLoop getCFRunLoop]);
|
||||
while (_currentJSThread && !_currentJSThread.finished) {
|
||||
[NSThread sleepForTimeInterval:0.01];
|
||||
}
|
||||
|
@ -162,18 +183,18 @@ static __weak RealmReact *s_currentRealmModule = nil;
|
|||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self shutdown];
|
||||
[self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)setBridge:(RCTBridge *)bridge {
|
||||
_bridge = bridge;
|
||||
|
||||
// shutdown the last instance of this module
|
||||
[s_currentRealmModule shutdown];
|
||||
s_currentRealmModule = self;
|
||||
static __weak RealmReact *s_currentModule = nil;
|
||||
[s_currentModule invalidate];
|
||||
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 DEBUG
|
||||
[self startRPC];
|
||||
|
@ -183,13 +204,16 @@ static __weak RealmReact *s_currentRealmModule = nil;
|
|||
}
|
||||
else {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
|
||||
[executor executeBlockOnJavaScriptQueue:^{
|
||||
RealmReact *self = weakSelf;
|
||||
__typeof__(self) self = weakSelf;
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->_currentJSThread = [NSThread currentThread];
|
||||
self->_currentJSRunLoop = [NSRunLoop currentRunLoop];
|
||||
|
||||
JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(executor, true);
|
||||
RJSInitializeInContext(ctx);
|
||||
}];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -4,18 +4,37 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var Realm = require('realm');
|
||||
var RealmTests = require('realm-tests');
|
||||
const React = require('react-native');
|
||||
const Realm = require('realm');
|
||||
const RealmTests = require('realm-tests');
|
||||
|
||||
var {
|
||||
const {
|
||||
AppRegistry,
|
||||
NativeAppEventEmitter,
|
||||
NativeModules,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
View,
|
||||
} = 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() {
|
||||
let testNames = RealmTests.getTestNames();
|
||||
|
||||
|
@ -46,7 +65,7 @@ function runTests() {
|
|||
}
|
||||
}
|
||||
|
||||
var ReactTests = React.createClass({
|
||||
class ReactTests extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
@ -60,7 +79,7 @@ var ReactTests = React.createClass({
|
|||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
|
|
|
@ -18,7 +18,7 @@ static NSString * const RCTDevMenuKey = @"RCTDevMenu";
|
|||
NSMutableDictionary *settings = [([defaults dictionaryForKey:RCTDevMenuKey] ?: @{}) mutableCopy];
|
||||
NSMutableDictionary *domain = [[defaults volatileDomainForName:NSArgumentDomain] mutableCopy];
|
||||
|
||||
settings[@"executorClass"] = [defaults boolForKey:RealmReactEnableChromeDebuggingKey] ? @"RCTWebSocketExecutor" : @"RCTContextExecutor";
|
||||
settings[@"executorClass"] = [defaults boolForKey:RealmReactEnableChromeDebuggingKey] ? @"RCTWebSocketExecutor" : @"RCTJSCExecutor";
|
||||
domain[RCTDevMenuKey] = settings;
|
||||
|
||||
// Re-register the arguments domain (highest precedent and volatile) with our new overridden settings.
|
||||
|
|
|
@ -6,12 +6,18 @@
|
|||
#import "RCTJavaScriptExecutor.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTDevMenu.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
|
||||
@import RealmReact;
|
||||
|
||||
extern void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack);
|
||||
extern NSMutableArray *RCTGetModuleClasses(void);
|
||||
|
||||
@interface RCTBridge ()
|
||||
+ (instancetype)currentBridge;
|
||||
- (void)setUp;
|
||||
@end
|
||||
|
||||
@interface RealmReactTests : RealmJSTests
|
||||
@end
|
||||
|
||||
|
@ -26,41 +32,40 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
}
|
||||
|
||||
+ (Class)executorClass {
|
||||
return NSClassFromString(@"RCTContextExecutor");
|
||||
return NSClassFromString(@"RCTJSCExecutor");
|
||||
}
|
||||
|
||||
+ (NSString *)classNameSuffix {
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (id<RCTJavaScriptExecutor>)currentExecutor {
|
||||
+ (RCTBridge *)currentBridge {
|
||||
Class executorClass = [self executorClass];
|
||||
if (!executorClass) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
static RCTBridge *s_bridge;
|
||||
if (!s_bridge) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
|
||||
RCTBridge *bridge = [RCTBridge currentBridge];
|
||||
if (!bridge.valid) {
|
||||
[self waitForNotification:RCTJavaScriptDidLoadNotification];
|
||||
bridge = [RCTBridge currentBridge];
|
||||
}
|
||||
|
||||
if (!s_bridge.valid) {
|
||||
NSNotification *notification = [self waitForNotification:RCTJavaScriptDidLoadNotification];
|
||||
s_bridge = notification.userInfo[@"bridge"];
|
||||
assert(s_bridge);
|
||||
if (bridge.executorClass != executorClass) {
|
||||
bridge.executorClass = executorClass;
|
||||
|
||||
RCTBridge *parentBridge = [bridge valueForKey:@"parentBridge"];
|
||||
[parentBridge invalidate];
|
||||
[parentBridge setUp];
|
||||
|
||||
return [self currentBridge];
|
||||
}
|
||||
|
||||
if (s_bridge.executorClass != executorClass) {
|
||||
s_bridge.executorClass = executorClass;
|
||||
[s_bridge reload];
|
||||
return bridge;
|
||||
}
|
||||
|
||||
// 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"];
|
||||
+ (id<RCTJavaScriptExecutor>)currentExecutor {
|
||||
return [[self currentBridge] valueForKey:@"javaScriptExecutor"];
|
||||
}
|
||||
|
||||
+ (XCTestSuite *)defaultTestSuite {
|
||||
|
@ -78,11 +83,9 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(ctx, false);
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *testCaseNames = [self invokeMethod:@"getTestNames" arguments:nil error:&error];
|
||||
|
||||
if (error || !testCaseNames.count) {
|
||||
NSLog(@"Error from calling getTestNames() - %@", error ?: @"None returned");
|
||||
NSDictionary *testCaseNames = [self waitForEvent:@"realm-test-names"];
|
||||
if (!testCaseNames.count) {
|
||||
NSLog(@"ERROR: No test names were provided by the JS");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -128,34 +131,24 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
}
|
||||
}
|
||||
|
||||
+ (id)invokeMethod:(NSString *)method arguments:(NSArray *)arguments error:(NSError * __strong *)outError {
|
||||
id<RCTJavaScriptExecutor> executor = [self currentExecutor];
|
||||
+ (id)waitForEvent:(NSString *)eventName {
|
||||
__weak RealmReact *realmModule = [[self currentBridge] moduleForClass:[RealmReact class]];
|
||||
assert(realmModule);
|
||||
|
||||
__block BOOL condition = NO;
|
||||
__block id result;
|
||||
__block RealmReactEventHandler handler;
|
||||
|
||||
[executor executeJSCall:@"realm-tests/index.js" method:method arguments:(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;
|
||||
__block __weak RealmReactEventHandler weakHandler = handler = ^(id object) {
|
||||
[realmModule removeListenerForEvent:eventName handler:weakHandler];
|
||||
|
||||
if (!error) {
|
||||
error = [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: @"unknown JS error"}];
|
||||
}
|
||||
}
|
||||
condition = YES;
|
||||
result = object;
|
||||
};
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
condition = YES;
|
||||
result = json;
|
||||
|
||||
if (error && outError) {
|
||||
*outError = error;
|
||||
}
|
||||
});
|
||||
}];
|
||||
[realmModule addListenerForEvent:eventName handler:handler];
|
||||
|
||||
[self waitForCondition:&condition];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -167,12 +160,12 @@ extern NSMutableArray *RCTGetModuleClasses(void);
|
|||
module = [module substringToIndex:(module.length - suffix.length)];
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
[self.class invokeMethod:@"runTest" arguments:@[module, method] error:&error];
|
||||
RCTBridge *bridge = [self.class currentBridge];
|
||||
[bridge.eventDispatcher sendAppEventWithName:@"realm-run-test" body:@{@"suite": module, @"name": method}];
|
||||
|
||||
id error = [self.class waitForEvent:@"realm-test-finished"];
|
||||
if (error) {
|
||||
// TODO: Parse and use localizedFailureReason info once we can source map the failure location in JS.
|
||||
[self recordFailureWithDescription:error.localizedDescription inFile:@(__FILE__) atLine:__LINE__ expected:YES];
|
||||
[self recordFailureWithDescription:[error description] inFile:@(__FILE__) atLine:__LINE__ expected:YES];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"start": "react-native start"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-native": "0.16.0",
|
||||
"react-native": "0.18.0-rc",
|
||||
"realm": "file:../..",
|
||||
"realm-tests": "file:../lib"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue