252 lines
7.6 KiB
Objective-C
252 lines
7.6 KiB
Objective-C
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2016 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 "RealmJSTests.h"
|
|
#import "RCTJavaScriptExecutor.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTDevMenu.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#import "RCTJavaScriptLoader.h"
|
|
#import "RCTLog.h"
|
|
|
|
@import RealmReact;
|
|
|
|
extern void JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(JSGlobalContextRef ctx, bool includesNativeCallStack);
|
|
extern NSMutableArray *RCTGetModuleClasses(void);
|
|
|
|
@interface RCTBridge ()
|
|
+ (instancetype)currentBridge;
|
|
- (void)setUp;
|
|
@end
|
|
|
|
@interface RCTDevMenuDisabler : RCTDevMenu
|
|
@end
|
|
|
|
@interface RealmReactTests : RealmJSTests
|
|
@end
|
|
|
|
@interface RealmReactChromeTests : RealmReactTests
|
|
@end
|
|
|
|
|
|
@implementation RCTDevMenuDisabler
|
|
|
|
+ (void)load {
|
|
// +[RCTDevMenu load] is guaranteed to have been called since it's the superclass.
|
|
// We remove it since it interferes with us fully controlling the executor class.
|
|
NSMutableArray *moduleClasses = RCTGetModuleClasses();
|
|
[moduleClasses removeObject:[RCTDevMenu class]];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation RealmReactTests
|
|
|
|
+ (void)load {
|
|
RCTAddLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
|
NSAssert(level < RCTLogLevelError, RCTFormatLog(nil, level, fileName, lineNumber, message));
|
|
});
|
|
}
|
|
|
|
+ (Class)executorClass {
|
|
return NSClassFromString(@"RCTJSCExecutor");
|
|
}
|
|
|
|
+ (NSString *)classNameSuffix {
|
|
return @"";
|
|
}
|
|
|
|
+ (RCTBridge *)currentBridge {
|
|
Class executorClass = [self executorClass];
|
|
if (!executorClass) {
|
|
return nil;
|
|
}
|
|
|
|
RCTBridge *bridge = [RCTBridge currentBridge];
|
|
if (!bridge.valid) {
|
|
[self waitForNotification:RCTJavaScriptDidLoadNotification];
|
|
bridge = [RCTBridge currentBridge];
|
|
}
|
|
|
|
if (bridge.executorClass != executorClass) {
|
|
bridge.executorClass = executorClass;
|
|
|
|
RCTBridge *parentBridge = [bridge valueForKey:@"parentBridge"];
|
|
[parentBridge invalidate];
|
|
[parentBridge setUp];
|
|
|
|
return [self currentBridge];
|
|
}
|
|
|
|
return bridge;
|
|
}
|
|
|
|
+ (id<RCTJavaScriptExecutor>)currentExecutor {
|
|
return [[self currentBridge] valueForKey:@"javaScriptExecutor"];
|
|
}
|
|
|
|
+ (XCTestSuite *)defaultTestSuite {
|
|
XCTestSuite *suite = [super defaultTestSuite];
|
|
id<RCTJavaScriptExecutor> executor = [self currentExecutor];
|
|
|
|
// The executor may be nil if the executorClass was not found (i.e. release build).
|
|
if (!executor) {
|
|
return suite;
|
|
}
|
|
|
|
// FIXME: Remove this nonsense once the crashes go away when a test fails!
|
|
JSGlobalContextRef ctx = RealmReactGetJSGlobalContextForExecutor(executor, false);
|
|
if (ctx) {
|
|
JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions(ctx, false);
|
|
}
|
|
|
|
NSDictionary *testCaseNames = [self waitForEvent:@"realm-test-names"];
|
|
NSAssert(testCaseNames.count, @"No test names were provided by the JS");
|
|
|
|
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;
|
|
}
|
|
|
|
for (XCTestSuite *testSuite in [self testSuitesFromDictionary:testCaseNames]) {
|
|
[suite addTest:testSuite];
|
|
}
|
|
|
|
return suite;
|
|
}
|
|
|
|
+ (NSNotification *)waitForNotification:(NSString *)notificationName {
|
|
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;
|
|
}];
|
|
|
|
@try {
|
|
[self waitForCondition:&condition description:notificationName];
|
|
} @finally {
|
|
[nc removeObserver:token];
|
|
}
|
|
|
|
return notification;
|
|
}
|
|
|
|
+ (void)waitForCondition:(BOOL *)condition description:(NSString *)description {
|
|
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
|
|
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:30.0];
|
|
|
|
while (!*condition) {
|
|
if ([timeout timeIntervalSinceNow] < 0) {
|
|
@throw [NSException exceptionWithName:@"ConditionTimeout"
|
|
reason:[NSString stringWithFormat:@"Timed out waiting for: %@", description]
|
|
userInfo:nil];
|
|
}
|
|
|
|
@autoreleasepool {
|
|
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
[runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
[NSThread sleepForTimeInterval:0.01]; // Bad things may happen without some sleep.
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (id)waitForEvent:(NSString *)eventName {
|
|
__weak RealmReact *realmModule = [[self currentBridge] moduleForClass:[RealmReact class]];
|
|
NSAssert(realmModule, @"RealmReact module not found");
|
|
|
|
__block BOOL condition = NO;
|
|
__block id result;
|
|
__block RealmReactEventHandler handler;
|
|
|
|
__block __weak RealmReactEventHandler weakHandler = handler = ^(id object) {
|
|
[realmModule removeListenerForEvent:eventName handler:weakHandler];
|
|
|
|
condition = YES;
|
|
result = object;
|
|
};
|
|
|
|
[realmModule addListenerForEvent:eventName handler:handler];
|
|
|
|
[self waitForCondition:&condition description:eventName];
|
|
return result;
|
|
}
|
|
|
|
- (void)invokeTest {
|
|
RCTLogFunction logFunction = RCTGetLogFunction();
|
|
|
|
// Fail when React Native logs an error.
|
|
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
|
RCTDefaultLogFunction(level, source, fileName, lineNumber, message);
|
|
|
|
if (level >= RCTLogLevelError) {
|
|
NSString *type = (source == RCTLogSourceJavaScript) ? @"JS" : @"Native";
|
|
XCTFail(@"%@ Error: %@", type, RCTFormatLog(nil, level, fileName, lineNumber, message));
|
|
}
|
|
});
|
|
|
|
[super invokeTest];
|
|
|
|
RCTSetLogFunction(logFunction);
|
|
}
|
|
|
|
- (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)];
|
|
}
|
|
|
|
RCTBridge *bridge = [self.class currentBridge];
|
|
[bridge.eventDispatcher sendAppEventWithName:@"realm-run-test" body:@{@"suite": module, @"name": method}];
|
|
|
|
id error;
|
|
@try {
|
|
error = [self.class waitForEvent:@"realm-test-finished"];
|
|
} @catch (id exception) {
|
|
error = exception;
|
|
}
|
|
|
|
if (error) {
|
|
[self recordFailureWithDescription:[error description] inFile:@(__FILE__) atLine:__LINE__ expected:YES];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation RealmReactChromeTests
|
|
|
|
+ (Class)executorClass {
|
|
return NSClassFromString(@"RCTWebSocketExecutor");
|
|
}
|
|
|
|
+ (NSString *)classNameSuffix {
|
|
return @"_Chrome";
|
|
}
|
|
|
|
@end
|