diff --git a/examples/ReactExample/.eslintrc b/examples/ReactExample/.eslintrc index 44bd6ec5..2e863dc5 100644 --- a/examples/ReactExample/.eslintrc +++ b/examples/ReactExample/.eslintrc @@ -6,6 +6,18 @@ "ecmaFeatures": { "jsx": true }, + "globals": { + "cancelAnimationFrame": false, + "clearImmediate": false, + "clearInterval": false, + "clearTimeout": false, + "console": false, + "global": false, + "requestAnimationFrame": false, + "setImmediate": false, + "setInterval": false, + "setTimeout": false + }, "parser": "babel-eslint", "plugins": [ "react" diff --git a/examples/ReactExample/index.ios.js b/examples/ReactExample/index.ios.js index 77d06c34..b9c970ad 100644 --- a/examples/ReactExample/index.ios.js +++ b/examples/ReactExample/index.ios.js @@ -4,4 +4,6 @@ 'use strict'; +// Allow our test harness to test this app. +import './tests'; import './main'; diff --git a/examples/ReactExample/ios/ReactExample.xcodeproj/project.pbxproj b/examples/ReactExample/ios/ReactExample.xcodeproj/project.pbxproj index e9b358e9..4a7a9dfa 100644 --- a/examples/ReactExample/ios/ReactExample.xcodeproj/project.pbxproj +++ b/examples/ReactExample/ios/ReactExample.xcodeproj/project.pbxproj @@ -12,7 +12,6 @@ 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; - 027798491BBB2F1000C96559 /* ReactExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 027798481BBB2F1000C96559 /* ReactExampleTests.m */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; @@ -24,6 +23,8 @@ 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; F6C4656F1C48DBE900E79896 /* RealmReact.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6C465551C48D4C300E79896 /* RealmReact.framework */; }; F6C465701C48DBF700E79896 /* RealmReact.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F6C465551C48D4C300E79896 /* RealmReact.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + F6DC9DE51C519CFF00ED587E /* RealmJSTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F6DC9DE41C519CFF00ED587E /* RealmJSTests.mm */; }; + F6DC9DE71C519D2300ED587E /* RealmReactTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6DC9DE61C519D2300ED587E /* RealmReactTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -170,7 +171,6 @@ 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 0270BC9E1B7D04D700010E03 /* RealmJS.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RealmJS.xcodeproj; path = ../../../RealmJS.xcodeproj; sourceTree = ""; }; 027798461BBB2F1000C96559 /* ReactExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 027798481BBB2F1000C96559 /* ReactExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactExampleTests.m; sourceTree = ""; }; 0277984A1BBB2F1000C96559 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; @@ -184,6 +184,9 @@ 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + F6DC9DE31C519CFF00ED587E /* RealmJSTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RealmJSTests.h; path = ../../../../tests/ios/RealmJSTests.h; sourceTree = ""; }; + F6DC9DE41C519CFF00ED587E /* RealmJSTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RealmJSTests.mm; path = ../../../../tests/ios/RealmJSTests.mm; sourceTree = ""; }; + F6DC9DE61C519D2300ED587E /* RealmReactTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RealmReactTests.m; path = "../../../../tests/react-test-app/ios/ReactTests/RealmReactTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -258,7 +261,9 @@ 027798471BBB2F1000C96559 /* ReactExampleTests */ = { isa = PBXGroup; children = ( - 027798481BBB2F1000C96559 /* ReactExampleTests.m */, + F6DC9DE31C519CFF00ED587E /* RealmJSTests.h */, + F6DC9DE41C519CFF00ED587E /* RealmJSTests.mm */, + F6DC9DE61C519D2300ED587E /* RealmReactTests.m */, 0277984A1BBB2F1000C96559 /* Info.plist */, ); path = ReactExampleTests; @@ -653,7 +658,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 027798491BBB2F1000C96559 /* ReactExampleTests.m in Sources */, + F6DC9DE71C519D2300ED587E /* RealmReactTests.m in Sources */, + F6DC9DE51C519CFF00ED587E /* RealmJSTests.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -698,7 +704,6 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = ReactExampleTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/examples/ReactExample/ios/ReactExampleTests/ReactExampleTests.m b/examples/ReactExample/ios/ReactExampleTests/ReactExampleTests.m deleted file mode 100644 index 1d9d12eb..00000000 --- a/examples/ReactExample/ios/ReactExampleTests/ReactExampleTests.m +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2015 Realm Inc - All Rights Reserved - * Proprietary and Confidential - */ - -#import -#import - -#import "RCTLog.h" -#import "RCTRootView.h" - -#define TIMEOUT_SECONDS 10 -#define TEXT_TO_LOOK_FOR @"Todo Items" - -@interface ReactExampleTests : XCTestCase - -@end - -@implementation ReactExampleTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testLaunched -{ - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - //XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - -@end diff --git a/examples/ReactExample/tests/example-test.js b/examples/ReactExample/tests/example-test.js new file mode 100644 index 00000000..0fe02e94 --- /dev/null +++ b/examples/ReactExample/tests/example-test.js @@ -0,0 +1,16 @@ +/* Copyright 2016 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + +'use strict'; + +import { + getRootComponent, + assertChildExists, +} from './util'; + +export default { + async testTodoAppRendered() { + assertChildExists(await getRootComponent(), 'TodoApp'); + }, +} \ No newline at end of file diff --git a/examples/ReactExample/tests/index.js b/examples/ReactExample/tests/index.js new file mode 100644 index 00000000..03211a76 --- /dev/null +++ b/examples/ReactExample/tests/index.js @@ -0,0 +1,56 @@ +/* Copyright 2016 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + +'use strict'; + +import { + NativeAppEventEmitter, + NativeModules, +} from 'react-native'; + +import ExampleTest from './example-test'; + +const TESTS = { + ExampleTest, +}; + +const SPECIAL_METHODS = { + beforeEach: true, + afterEach: true, +}; + +// Listen for event to run a particular test. +NativeAppEventEmitter.addListener('realm-run-test', async ({suite, name}) => { + let testSuite = TESTS[suite]; + let testMethod = testSuite && testSuite[name]; + let error; + + try { + if (testMethod) { + await testMethod.call(testSuite); + } else if (!testSuite || !(name in SPECIAL_METHODS)) { + throw new Error('Missing test: ' + suite + '.' + 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', getTestNames()); +}, 0); + +function getTestNames() { + let testNames = {}; + + for (let suiteName in TESTS) { + let testSuite = TESTS[suiteName]; + testNames[suiteName] = Object.keys(testSuite); + } + + return testNames; +} diff --git a/examples/ReactExample/tests/util.js b/examples/ReactExample/tests/util.js new file mode 100644 index 00000000..eceeb51b --- /dev/null +++ b/examples/ReactExample/tests/util.js @@ -0,0 +1,59 @@ +/* Copyright 2016 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + +'use strict'; + +import React from 'react-native'; + +const rootComponentPromise = new Promise((resolve) => { + // Require internal module here so the promise is rejected if there is an error. + let Mount = require('react-native/Libraries/ReactNative/ReactNativeMount'); + let renderComponent = Mount.renderComponent; + + Mount.renderComponent = function() { + let component = renderComponent.apply(this, arguments); + + resolve(component); + return component; + }; +}); + +export function getRootComponent() { + return rootComponentPromise; +} + +export function assertChildExists(component, name) { + if (!findChildComponent(component, name)) { + throw new Error(name + ' not rendered'); + } +} + +export function findChildComponent(component, name) { + for (let child of traverseChildren(component)) { + if (child.type.name == name) { + return child; + } + } + return null; +} + +export function* traverseChildren(component) { + let props = component.props; + + // The hacky TopLevelWrapper has its props set to the root element. + if (props.props) { + props = props.props; + } + + let children = props.children; + if (!children) { + return; + } + + // ReactNative is missing React.Children.toArray() + for (let child of React.Children.map(children, (x) => x)) { + yield child; + yield* traverseChildren(child); + } +}