diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js
index 4337aee91..e6b7fa6d4 100644
--- a/Examples/2048/Game2048.js
+++ b/Examples/2048/Game2048.js
@@ -15,20 +15,20 @@ var {
View,
} = React;
-var GameBoard = require('./GameBoard');
+var GameBoard = require('GameBoard');
var TouchableBounce = require('TouchableBounce');
var BOARD_PADDING = 3;
var CELL_MARGIN = 4;
var CELL_SIZE = 60;
-var Cell = React.createClass({
- render: function() {
+class Cell extends React.Component {
+ render() {
return ;
}
-});
+}
-var Board = React.createClass({
+class Board extends React.Component {
render() {
return (
@@ -40,12 +40,10 @@ var Board = React.createClass({
);
}
-});
+}
-var Tile = React.createClass({
- mixins: [Animation.Mixin],
-
- calculateOffset() {
+class Tile extends React.Component {
+ calculateOffset(): {top: number; left: number; opacity: number} {
var tile = this.props.tile;
var pos = (i) => {
@@ -59,6 +57,7 @@ var Tile = React.createClass({
var offset = {
top: pos(tile.toRow()),
left: pos(tile.toColumn()),
+ opacity: 1,
};
if (tile.isNew()) {
@@ -68,18 +67,18 @@ var Tile = React.createClass({
animationPosition(tile.toColumn()),
animationPosition(tile.toRow()),
];
- this.startAnimation('this', 100, 0, 'easeInOutQuad', {position: point});
+ Animation.startAnimation(this.refs['this'], 100, 0, 'easeInOutQuad', {position: point});
}
return offset;
- },
+ }
componentDidMount() {
setTimeout(() => {
- this.startAnimation('this', 300, 0, 'easeInOutQuad', {scaleXY: [1, 1]});
- this.startAnimation('this', 100, 0, 'easeInOutQuad', {opacity: 1});
+ Animation.startAnimation(this.refs['this'], 300, 0, 'easeInOutQuad', {scaleXY: [1, 1]});
+ Animation.startAnimation(this.refs['this'], 100, 0, 'easeInOutQuad', {opacity: 1});
}, 0);
- },
+ }
render() {
var tile = this.props.tile;
@@ -103,9 +102,9 @@ var Tile = React.createClass({
);
}
-});
+}
-var GameEndOverlay = React.createClass({
+class GameEndOverlay extends React.Component {
render() {
var board = this.props.board;
@@ -127,27 +126,30 @@ var GameEndOverlay = React.createClass({
);
}
-});
+}
-var Game2048 = React.createClass({
- getInitialState() {
- return { board: new GameBoard() };
- },
+class Game2048 extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ board: new GameBoard(),
+ };
+ }
restartGame() {
- this.setState(this.getInitialState());
- },
+ this.setState({board: new GameBoard()});
+ }
- handleTouchStart(event) {
+ handleTouchStart(event: Object) {
if (this.state.board.hasWon()) {
return;
}
this.startX = event.nativeEvent.pageX;
this.startY = event.nativeEvent.pageY;
- },
+ }
- handleTouchEnd(event) {
+ handleTouchEnd(event: Object) {
if (this.state.board.hasWon()) {
return;
}
@@ -165,7 +167,7 @@ var Game2048 = React.createClass({
if (direction !== -1) {
this.setState({board: this.state.board.move(direction)});
}
- },
+ }
render() {
var tiles = this.state.board.tiles
@@ -175,16 +177,16 @@ var Game2048 = React.createClass({
return (
+ onTouchStart={(event) => this.handleTouchStart(event)}
+ onTouchEnd={(event) => this.handleTouchEnd(event)}>
{tiles}
-
+ this.restartGame()} />
);
}
-});
+}
var styles = StyleSheet.create({
container: {
diff --git a/Examples/2048/main.m b/Examples/2048/main.m
index a43b55738..3c8987ce3 100644
--- a/Examples/2048/main.m
+++ b/Examples/2048/main.m
@@ -6,6 +6,6 @@
int main(int argc, char * argv[]) {
@autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
diff --git a/Examples/UIExplorer/AlertIOSExample.js b/Examples/UIExplorer/AlertIOSExample.js
new file mode 100644
index 000000000..a2b92d975
--- /dev/null
+++ b/Examples/UIExplorer/AlertIOSExample.js
@@ -0,0 +1,98 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+'use strict';
+
+var React = require('react-native');
+var {
+ StyleSheet,
+ View,
+ Text,
+ TouchableHighlight,
+ AlertIOS,
+} = React;
+
+exports.framework = 'React';
+exports.title = 'AlertIOS';
+exports.description = 'iOS alerts and action sheets';
+exports.examples = [{
+ title: 'Alerts',
+ render() {
+ return (
+
+ AlertIOS.alert(
+ 'Foo Title',
+ 'My Alert Msg'
+ )}>
+
+ Alert with message and default button
+
+
+ AlertIOS.alert(
+ null,
+ null,
+ [
+ {text: 'Button', onPress: () => console.log('Button Pressed!')},
+ ]
+ )}>
+
+ Alert with only one button
+
+
+ AlertIOS.alert(
+ 'Foo Title',
+ 'My Alert Msg',
+ [
+ {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
+ {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
+ ]
+ )}>
+
+ Alert with two buttons
+
+
+ AlertIOS.alert(
+ 'Foo Title',
+ null,
+ [
+ {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
+ {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
+ {text: 'Baz', onPress: () => console.log('Baz Pressed!')},
+ ]
+ )}>
+
+ Alert with 3 buttons
+
+
+ AlertIOS.alert(
+ 'Foo Title',
+ 'My Alert Msg',
+ '..............'.split('').map((dot, index) => ({
+ text: 'Button ' + index,
+ onPress: () => console.log('Pressed ' + index)
+ }))
+ )}>
+
+ Alert with too many buttons
+
+
+
+ );
+ },
+}];
+
+var styles = StyleSheet.create({
+ wrapper: {
+ borderRadius: 5,
+ marginBottom: 5,
+ },
+ button: {
+ backgroundColor: '#eeeeee',
+ padding: 10,
+ },
+});
diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index 59653055e..b7108681e 100644
--- a/Examples/UIExplorer/UIExplorerList.js
+++ b/Examples/UIExplorer/UIExplorerList.js
@@ -40,7 +40,9 @@ var EXAMPLES = [
require('./AsyncStorageExample'),
require('./CameraRollExample.ios'),
require('./MapViewExample'),
+ require('./WebViewExample'),
require('./AppStateIOSExample'),
+ require('./AlertIOSExample'),
require('./AdSupportIOSExample'),
require('./AppStateExample'),
require('./ActionSheetIOSExample'),
diff --git a/Examples/UIExplorer/WebViewExample.js b/Examples/UIExplorer/WebViewExample.js
new file mode 100644
index 000000000..4b7a1513d
--- /dev/null
+++ b/Examples/UIExplorer/WebViewExample.js
@@ -0,0 +1,264 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+'use strict';
+
+var React = require('react-native');
+var StyleSheet = require('StyleSheet');
+var {
+ ActivityIndicatorIOS,
+ StyleSheet,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View,
+ WebView
+} = React;
+
+var HEADER = '#3b5998';
+var BGWASH = 'rgba(255,255,255,0.8)';
+var DISABLED_WASH = 'rgba(255,255,255,0.25)';
+
+var TEXT_INPUT_REF = 'urlInput';
+var WEBVIEW_REF = 'webview';
+var DEFAULT_URL = 'https://m.facebook.com';
+
+var WebViewExample = React.createClass({
+
+ getInitialState: function() {
+ return {
+ url: DEFAULT_URL,
+ status: 'No Page Loaded',
+ backButtonEnabled: false,
+ forwardButtonEnabled: false,
+ loading: true,
+ };
+ },
+
+ handleTextInputChange: function(event) {
+ this.inputText = event.nativeEvent.text;
+ },
+
+ render: function() {
+ this.inputText = this.state.url;
+
+ return (
+
+
+
+
+
+ {'<'}
+
+
+
+
+
+
+ {'>'}
+
+
+
+
+
+
+
+ Go!
+
+
+
+
+
+
+ {this.state.status}
+
+
+ );
+ },
+
+ goBack: function() {
+ this.refs[WEBVIEW_REF].goBack();
+ },
+
+ goForward: function() {
+ this.refs[WEBVIEW_REF].goForward();
+ },
+
+ reload: function() {
+ this.refs[WEBVIEW_REF].reload();
+ },
+
+ onNavigationStateChange: function(navState) {
+ this.setState({
+ backButtonEnabled: navState.canGoBack,
+ forwardButtonEnabled: navState.canGoForward,
+ url: navState.url,
+ status: navState.title,
+ loading: navState.loading,
+ });
+ },
+
+ renderErrorView: function(errorDomain, errorCode, errorDesc) {
+ return (
+
+
+ Error loading page
+
+
+ {'Domain: ' + errorDomain}
+
+
+ {'Error Code: ' + errorCode}
+
+
+ {'Description: ' + errorDesc}
+
+
+ );
+ },
+
+ renderLoadingView: function() {
+ return (
+
+
+
+ );
+ },
+
+ onSubmitEditing: function(event) {
+ this.pressGoButton();
+ },
+
+ pressGoButton: function() {
+ var url = this.inputText.toLowerCase();
+ if (url === this.state.url) {
+ this.reload();
+ } else {
+ this.setState({
+ url: url,
+ });
+ }
+ // dismiss keyoard
+ this.refs[TEXT_INPUT_REF].blur();
+ },
+
+});
+
+var styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: HEADER,
+ },
+ addressBarRow: {
+ flexDirection: 'row',
+ padding: 8,
+ },
+ webView: {
+ backgroundColor: BGWASH,
+ height: 350,
+ },
+ addressBarTextInput: {
+ backgroundColor: BGWASH,
+ borderColor: 'transparent',
+ borderRadius: 3,
+ borderWidth: 1,
+ height: 24,
+ paddingLeft: 10,
+ paddingTop: 3,
+ paddingBottom: 3,
+ flex: 1,
+ fontSize: 14,
+ },
+ navButton: {
+ width: 20,
+ padding: 3,
+ marginRight: 3,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: BGWASH,
+ borderColor: 'transparent',
+ borderRadius: 3,
+ },
+ disabledButton: {
+ width: 20,
+ padding: 3,
+ marginRight: 3,
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: DISABLED_WASH,
+ borderColor: 'transparent',
+ borderRadius: 3,
+ },
+ goButton: {
+ height: 24,
+ padding: 3,
+ marginLeft: 8,
+ alignItems: 'center',
+ backgroundColor: BGWASH,
+ borderColor: 'transparent',
+ borderRadius: 3,
+ alignSelf: 'stretch',
+ },
+ loadingView: {
+ backgroundColor: BGWASH,
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ errorContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: BGWASH,
+ },
+ errorTextTitle: {
+ fontSize: 15,
+ fontWeight: 'bold',
+ marginBottom: 10,
+ },
+ errorText: {
+ fontSize: 14,
+ textAlign: 'center',
+ marginBottom: 2,
+ },
+ statusBar: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingLeft: 5,
+ height: 22,
+ },
+ statusBarText: {
+ color: 'white',
+ fontSize: 13,
+ },
+ spinner: {
+ width: 20,
+ marginRight: 6,
+ },
+});
+
+exports.title = '';
+exports.description = 'Base component to display web content';
+exports.examples = [
+ {
+ title: 'WebView',
+ render() { return ; }
+ }
+];
diff --git a/IntegrationTests/AppDelegate.h b/IntegrationTests/AppDelegate.h
new file mode 100644
index 000000000..f0ec66bdb
--- /dev/null
+++ b/IntegrationTests/AppDelegate.h
@@ -0,0 +1,10 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+
+@interface AppDelegate : UIResponder
+
+@property (nonatomic, strong) UIWindow *window;
+
+@end
+
diff --git a/IntegrationTests/AppDelegate.m b/IntegrationTests/AppDelegate.m
new file mode 100644
index 000000000..db988faf8
--- /dev/null
+++ b/IntegrationTests/AppDelegate.m
@@ -0,0 +1,44 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "AppDelegate.h"
+
+#import "RCTRootView.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ NSURL *jsCodeLocation;
+ RCTRootView *rootView = [[RCTRootView alloc] init];
+
+ // 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, and make sure your computer and
+ // iOS device are on the same Wi-Fi network.
+ jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/IntegrationTests/IntegrationTestsApp.includeRequire.runModule.bundle?dev=true"];
+
+ // OPTION 2
+ // Load from pre-bundled file on disk. To re-generate the static bundle, run
+ //
+ // $ curl http://localhost:8081/IntegrationTests/IntegrationTestsApp.includeRequire.runModule.bundle -o main.jsbundle
+ //
+ // and uncomment the next following line
+ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+
+ rootView.scriptURL = jsCodeLocation;
+ rootView.moduleName = @"IntegrationTestsApp";
+
+ 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/IntegrationTests/Base.lproj/LaunchScreen.xib b/IntegrationTests/Base.lproj/LaunchScreen.xib
new file mode 100644
index 000000000..52cc0828d
--- /dev/null
+++ b/IntegrationTests/Base.lproj/LaunchScreen.xib
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json b/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..413d60e76
--- /dev/null
+++ b/IntegrationTests/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,44 @@
+{
+ "images" : [
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x-1.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x-2.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x-3.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x-5.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "uie_icon@2x-4.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-1.png differ
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-2.png differ
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-3.png differ
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-4.png differ
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x-5.png differ
diff --git a/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png
new file mode 100644
index 000000000..08a42699d
Binary files /dev/null and b/IntegrationTests/Images.xcassets/AppIcon.appiconset/uie_icon@2x.png differ
diff --git a/IntegrationTests/Info.plist b/IntegrationTests/Info.plist
new file mode 100644
index 000000000..245054621
--- /dev/null
+++ b/IntegrationTests/Info.plist
@@ -0,0 +1,42 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ com.facebook.$(PRODUCT_NAME:rfc1034identifier)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ NSLocationWhenInUseUsageDescription
+ You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*!
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/IntegrationTests/IntegrationTestHarnessTest.js b/IntegrationTests/IntegrationTestHarnessTest.js
new file mode 100644
index 000000000..5b6e78838
--- /dev/null
+++ b/IntegrationTests/IntegrationTestHarnessTest.js
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+'use strict';
+
+var RCTTestModule = require('NativeModules').RCTTestModule;
+var React = require('react-native');
+var {
+ Text,
+ View,
+} = React;
+
+var IntegrationTestHarnessTest = React.createClass({
+ propTypes: {
+ shouldThrow: React.PropTypes.bool,
+ waitOneFrame: React.PropTypes.bool,
+ },
+
+ getInitialState() {
+ return {
+ done: false,
+ };
+ },
+
+ componentDidMount() {
+ if (this.props.waitOneFrame) {
+ requestAnimationFrame(this.runTest);
+ } else {
+ this.runTest();
+ }
+ },
+
+ runTest() {
+ if (this.props.shouldThrow) {
+ throw new Error('Throwing error because shouldThrow');
+ }
+ if (!RCTTestModule) {
+ throw new Error('RCTTestModule is not registered.');
+ } else if (!RCTTestModule.markTestCompleted) {
+ throw new Error('RCTTestModule.markTestCompleted not defined.');
+ }
+ this.setState({done: true}, RCTTestModule.markTestCompleted);
+ },
+
+ render() {
+ return (
+
+
+ {this.constructor.displayName + ': '}
+ {this.state.done ? 'Done' : 'Testing...'}
+
+
+ );
+ }
+});
+
+module.exports = IntegrationTestHarnessTest;
diff --git a/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..c850360b1
--- /dev/null
+++ b/IntegrationTests/IntegrationTests.xcodeproj/project.pbxproj
@@ -0,0 +1,651 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 004D28A31AAF61C70097A701 /* IntegrationTestsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */; };
+ 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
+ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
+ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+ 580C37601AB0F6180015E709 /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37461AB0F5320015E709 /* libReactKit.a */; };
+ 580C37611AB0F61E0015E709 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */; };
+ 580C37621AB0F6260015E709 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */; };
+ 580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C37551AB0F56E0015E709 /* libRCTImage.a */; };
+ 580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375A1AB0F5970015E709 /* libRCTNetwork.a */; };
+ 580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C375F1AB0F5D10015E709 /* libRCTText.a */; };
+ 580C37921AB1090B0015E709 /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 580C378F1AB104B00015E709 /* libRCTTest.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
+ remoteInfo = IntegrationTests;
+ };
+ 580C37451AB0F5320015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192;
+ remoteInfo = ReactKit;
+ };
+ 580C374A1AB0F54A0015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
+ remoteInfo = RCTAdSupport;
+ };
+ 580C374F1AB0F55C0015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 134814201AA4EA6300B7C361;
+ remoteInfo = RCTGeolocation;
+ };
+ 580C37541AB0F56E0015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
+ remoteInfo = RCTImage;
+ };
+ 580C37591AB0F5970015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 134180261AA91779003F314A /* RCTNetwork.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 58B511DB1A9E6C8500147676;
+ remoteInfo = RCTNetwork;
+ };
+ 580C375E1AB0F5D10015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 58B5119B1A9E6C1200147676;
+ remoteInfo = RCTText;
+ };
+ 580C378E1AB104B00015E709 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */;
+ proxyType = 2;
+ remoteGlobalIDString = 580C376F1AB104AF0015E709;
+ remoteInfo = RCTTest;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTestsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 004D28A11AAF61C70097A701 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IntegrationTestsTests.m; sourceTree = ""; };
+ 13417FE31AA91428003F314A /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; };
+ 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../Libraries/Text/RCTText.xcodeproj; sourceTree = ""; };
+ 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../ReactKit/ReactKit.xcodeproj; sourceTree = ""; };
+ 134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; };
+ 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = ""; };
+ 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../Libraries/GeoLocation/RCTGeolocation.xcodeproj; sourceTree = ""; };
+ 13B07F961A680F5B00A75B9A /* IntegrationTests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IntegrationTests.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
+ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
+ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 004D289B1AAF61C70097A701 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 580C37601AB0F6180015E709 /* libReactKit.a in Frameworks */,
+ 580C37611AB0F61E0015E709 /* libRCTAdSupport.a in Frameworks */,
+ 580C37621AB0F6260015E709 /* libRCTGeolocation.a in Frameworks */,
+ 580C37631AB0F62C0015E709 /* libRCTImage.a in Frameworks */,
+ 580C37641AB0F6350015E709 /* libRCTNetwork.a in Frameworks */,
+ 580C37651AB0F63E0015E709 /* libRCTText.a in Frameworks */,
+ 580C37921AB1090B0015E709 /* libRCTTest.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 004D289F1AAF61C70097A701 /* IntegrationTestsTests */ = {
+ isa = PBXGroup;
+ children = (
+ 004D28A21AAF61C70097A701 /* IntegrationTestsTests.m */,
+ 004D28A01AAF61C70097A701 /* Supporting Files */,
+ );
+ path = IntegrationTestsTests;
+ sourceTree = "";
+ };
+ 004D28A01AAF61C70097A701 /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 004D28A11AAF61C70097A701 /* Info.plist */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ 1316A21D1AA397F400C0188E /* Libraries */ = {
+ isa = PBXGroup;
+ children = (
+ 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */,
+ 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */,
+ 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */,
+ 13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
+ 134180261AA91779003F314A /* RCTNetwork.xcodeproj */,
+ 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */,
+ 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */,
+ );
+ name = Libraries;
+ sourceTree = "";
+ };
+ 13B07FAE1A68108700A75B9A /* IntegrationTests */ = {
+ isa = PBXGroup;
+ children = (
+ 13B07FAF1A68108700A75B9A /* AppDelegate.h */,
+ 13B07FB01A68108700A75B9A /* AppDelegate.m */,
+ 13B07FB51A68108700A75B9A /* Images.xcassets */,
+ 13B07FB61A68108700A75B9A /* Info.plist */,
+ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
+ 13B07FB71A68108700A75B9A /* main.m */,
+ );
+ name = IntegrationTests;
+ sourceTree = "";
+ };
+ 580C37421AB0F5320015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C37461AB0F5320015E709 /* libReactKit.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C37471AB0F54A0015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C374C1AB0F55C0015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C37511AB0F56E0015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C37551AB0F56E0015E709 /* libRCTImage.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C37561AB0F5970015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C375A1AB0F5970015E709 /* libRCTNetwork.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C375B1AB0F5D10015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C375F1AB0F5D10015E709 /* libRCTText.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 580C378A1AB104AF0015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C378F1AB104B00015E709 /* libRCTTest.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 83CBB9F61A601CBA00E9B192 = {
+ isa = PBXGroup;
+ children = (
+ 13B07FAE1A68108700A75B9A /* IntegrationTests */,
+ 1316A21D1AA397F400C0188E /* Libraries */,
+ 004D289F1AAF61C70097A701 /* IntegrationTestsTests */,
+ 83CBBA001A601CBA00E9B192 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 83CBBA001A601CBA00E9B192 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 13B07F961A680F5B00A75B9A /* IntegrationTests.app */,
+ 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 004D289D1AAF61C70097A701 /* IntegrationTestsTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "IntegrationTestsTests" */;
+ buildPhases = (
+ 004D289A1AAF61C70097A701 /* Sources */,
+ 004D289B1AAF61C70097A701 /* Frameworks */,
+ 004D289C1AAF61C70097A701 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 004D28A51AAF61C70097A701 /* PBXTargetDependency */,
+ );
+ name = IntegrationTestsTests;
+ productName = IntegrationTestsTests;
+ productReference = 004D289E1AAF61C70097A701 /* IntegrationTestsTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 13B07F861A680F5B00A75B9A /* IntegrationTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "IntegrationTests" */;
+ buildPhases = (
+ 13B07F871A680F5B00A75B9A /* Sources */,
+ 13B07F8C1A680F5B00A75B9A /* Frameworks */,
+ 13B07F8E1A680F5B00A75B9A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = IntegrationTests;
+ productName = "Hello World";
+ productReference = 13B07F961A680F5B00A75B9A /* IntegrationTests.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 83CBB9F71A601CBA00E9B192 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0610;
+ ORGANIZATIONNAME = Facebook;
+ TargetAttributes = {
+ 004D289D1AAF61C70097A701 = {
+ CreatedOnToolsVersion = 6.1.1;
+ TestTargetID = 13B07F861A680F5B00A75B9A;
+ };
+ };
+ };
+ buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "IntegrationTests" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 83CBB9F61A601CBA00E9B192;
+ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
+ projectDirPath = "";
+ projectReferences = (
+ {
+ ProductGroup = 580C37471AB0F54A0015E709 /* Products */;
+ ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C374C1AB0F55C0015E709 /* Products */;
+ ProjectRef = 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C37511AB0F56E0015E709 /* Products */;
+ ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C37561AB0F5970015E709 /* Products */;
+ ProjectRef = 134180261AA91779003F314A /* RCTNetwork.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C378A1AB104AF0015E709 /* Products */;
+ ProjectRef = 580C37891AB104AF0015E709 /* RCTTest.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C375B1AB0F5D10015E709 /* Products */;
+ ProjectRef = 13417FEA1AA914B8003F314A /* RCTText.xcodeproj */;
+ },
+ {
+ ProductGroup = 580C37421AB0F5320015E709 /* Products */;
+ ProjectRef = 13417FFA1AA91531003F314A /* ReactKit.xcodeproj */;
+ },
+ );
+ projectRoot = "";
+ targets = (
+ 13B07F861A680F5B00A75B9A /* IntegrationTests */,
+ 004D289D1AAF61C70097A701 /* IntegrationTestsTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXReferenceProxy section */
+ 580C37461AB0F5320015E709 /* libReactKit.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libReactKit.a;
+ remoteRef = 580C37451AB0F5320015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C374B1AB0F54A0015E709 /* libRCTAdSupport.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTAdSupport.a;
+ remoteRef = 580C374A1AB0F54A0015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C37501AB0F55C0015E709 /* libRCTGeolocation.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTGeolocation.a;
+ remoteRef = 580C374F1AB0F55C0015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C37551AB0F56E0015E709 /* libRCTImage.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTImage.a;
+ remoteRef = 580C37541AB0F56E0015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C375A1AB0F5970015E709 /* libRCTNetwork.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTNetwork.a;
+ remoteRef = 580C37591AB0F5970015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C375F1AB0F5D10015E709 /* libRCTText.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTText.a;
+ remoteRef = 580C375E1AB0F5D10015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+ 580C378F1AB104B00015E709 /* libRCTTest.a */ = {
+ isa = PBXReferenceProxy;
+ fileType = archive.ar;
+ path = libRCTTest.a;
+ remoteRef = 580C378E1AB104B00015E709 /* PBXContainerItemProxy */;
+ sourceTree = BUILT_PRODUCTS_DIR;
+ };
+/* End PBXReferenceProxy section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 004D289C1AAF61C70097A701 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 13B07F8E1A680F5B00A75B9A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
+ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 004D289A1AAF61C70097A701 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 004D28A31AAF61C70097A701 /* IntegrationTestsTests.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 13B07F871A680F5B00A75B9A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
+ 13B07FC11A68108700A75B9A /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 004D28A51AAF61C70097A701 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 13B07F861A680F5B00A75B9A /* IntegrationTests */;
+ targetProxy = 004D28A41AAF61C70097A701 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 13B07FB21A68108700A75B9A /* Base */,
+ );
+ name = LaunchScreen.xib;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 004D28A61AAF61C70097A701 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ INFOPLIST_FILE = IntegrationTestsTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegrationTests.app/IntegrationTests";
+ };
+ name = Debug;
+ };
+ 004D28A71AAF61C70097A701 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SDKROOT)/Developer/Library/Frameworks",
+ "$(DEVELOPER_FRAMEWORKS_DIR)",
+ );
+ INFOPLIST_FILE = IntegrationTestsTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/IntegrationTests.app/IntegrationTests";
+ };
+ name = Release;
+ };
+ 13B07F941A680F5B00A75B9A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../../ReactKit/**",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = "$(inherited)";
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = IntegrationTests;
+ };
+ name = Debug;
+ };
+ 13B07F951A680F5B00A75B9A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../../ReactKit/**",
+ );
+ INFOPLIST_FILE = "$(SRCROOT)/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = "$(inherited)";
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = IntegrationTests;
+ };
+ name = Release;
+ };
+ 83CBBA201A601CBA00E9B192 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../ReactKit/**",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 7.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 83CBBA211A601CBA00E9B192 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../ReactKit/**",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 7.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 004D28AD1AAF61C70097A701 /* Build configuration list for PBXNativeTarget "IntegrationTestsTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 004D28A61AAF61C70097A701 /* Debug */,
+ 004D28A71AAF61C70097A701 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "IntegrationTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 13B07F941A680F5B00A75B9A /* Debug */,
+ 13B07F951A680F5B00A75B9A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "IntegrationTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 83CBBA201A601CBA00E9B192 /* Debug */,
+ 83CBBA211A601CBA00E9B192 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
+}
diff --git a/IntegrationTests/IntegrationTestsApp.js b/IntegrationTests/IntegrationTestsApp.js
new file mode 100644
index 000000000..ee60aa388
--- /dev/null
+++ b/IntegrationTests/IntegrationTestsApp.js
@@ -0,0 +1,84 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule IntegrationTestsApp
+ */
+'use strict';
+
+var React = require('react-native');
+
+var {
+ AppRegistry,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} = React;
+
+var TESTS = [
+ require('./IntegrationTestHarnessTest'),
+];
+
+TESTS.forEach(
+ (test) => AppRegistry.registerComponent(test.displayName, () => test)
+);
+
+var IntegrationTestsApp = React.createClass({
+ getInitialState: function() {
+ return {
+ test: null,
+ };
+ },
+ render: function() {
+ if (this.state.test) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+ Click on a test to run it in this shell for easier debugging and
+ development. Run all tests in the testing envirnment with cmd+U in
+ Xcode.
+
+
+
+ {TESTS.map((test) => [
+ this.setState({test})}>
+
+
+ {test.displayName}
+
+
+ ,
+
+ ])}
+
+
+ );
+ }
+});
+
+var styles = StyleSheet.create({
+ container: {
+ backgroundColor: 'white',
+ marginTop: 40,
+ margin: 15,
+ },
+ row: {
+ padding: 10,
+ },
+ testName: {
+ fontWeight: 'bold',
+ },
+ separator: {
+ height: 1,
+ backgroundColor: '#bbbbbb',
+ },
+});
+
+AppRegistry.registerComponent('IntegrationTestsApp', () => IntegrationTestsApp);
diff --git a/IntegrationTests/IntegrationTestsTests/Info.plist b/IntegrationTests/IntegrationTestsTests/Info.plist
new file mode 100644
index 000000000..87e3a6175
--- /dev/null
+++ b/IntegrationTests/IntegrationTestsTests/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ com.facebook.$(PRODUCT_NAME:rfc1034identifier)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+
+
diff --git a/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m b/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m
new file mode 100644
index 000000000..9d9020f7d
--- /dev/null
+++ b/IntegrationTests/IntegrationTestsTests/IntegrationTestsTests.m
@@ -0,0 +1,40 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+#import
+
+#import
+
+#import "RCTAssert.h"
+
+@interface IntegrationTestsTests : XCTestCase
+
+@end
+
+@implementation IntegrationTestsTests {
+ RCTTestRunner *_runner;
+}
+
+- (void)setUp
+{
+ _runner = [[RCTTestRunner alloc] initWithApp:@"IntegrationTests/IntegrationTestsApp"];
+}
+
+- (void)testTheTester
+{
+ [_runner runTest:@"IntegrationTestHarnessTest"];
+}
+
+- (void)testTheTester_waitOneFrame
+{
+ [_runner runTest:@"IntegrationTestHarnessTest" initialProps:@{@"waitOneFrame": @YES} expectErrorBlock:nil];
+}
+
+- (void)testTheTester_ExpectError
+{
+ [_runner runTest:@"IntegrationTestHarnessTest"
+ initialProps:@{@"shouldThrow": @YES}
+ expectErrorRegex:[NSRegularExpression regularExpressionWithPattern:@"because shouldThrow" options:0 error:nil]];
+}
+
+@end
diff --git a/IntegrationTests/main.m b/IntegrationTests/main.m
new file mode 100644
index 000000000..357a233b1
--- /dev/null
+++ b/IntegrationTests/main.m
@@ -0,0 +1,10 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Libraries/Animation/Animation.js b/Libraries/Animation/Animation.js
index 80029f148..30624266d 100644
--- a/Libraries/Animation/Animation.js
+++ b/Libraries/Animation/Animation.js
@@ -23,10 +23,11 @@ var Animation = {
): number {
var nodeHandle = +node.getNodeHandle();
var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing);
- RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties);
+ var tag: number = RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties);
+ return tag;
},
- stopAnimation: function(tag) {
+ stopAnimation: function(tag: number) {
RCTAnimationManager.stopAnimation(tag);
},
};
diff --git a/Libraries/Animation/AnimationMixin.js b/Libraries/Animation/AnimationMixin.js
index ff29e2735..56f63fb8e 100644
--- a/Libraries/Animation/AnimationMixin.js
+++ b/Libraries/Animation/AnimationMixin.js
@@ -34,7 +34,8 @@ var AnimationMixin = {
var nodeHandle = +ref.getNodeHandle();
var easingSample = AnimationUtils.evaluateEasingFunction(duration, easing);
- RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties);
+ var tag: number = RCTAnimationManager.startAnimation(nodeHandle, AnimationUtils.allocateTag(), duration, delay, easingSample, properties);
+ return tag;
},
stopAnimation: function(tag: number) {
diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js
new file mode 100644
index 000000000..8190258ae
--- /dev/null
+++ b/Libraries/Components/WebView/WebView.android.js
@@ -0,0 +1,169 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule WebView
+ */
+'use strict';
+
+var EdgeInsetsPropType = require('EdgeInsetsPropType');
+var React = require('React');
+var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
+var StyleSheet = require('StyleSheet');
+var View = require('View');
+
+var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
+var keyMirror = require('keyMirror');
+var merge = require('merge');
+
+var PropTypes = React.PropTypes;
+var RKUIManager = require('NativeModules').RKUIManager;
+
+var RK_WEBVIEW_REF = 'webview';
+
+var WebViewState = keyMirror({
+ IDLE: null,
+ LOADING: null,
+ ERROR: null,
+});
+
+var WebView = React.createClass({
+
+ propTypes: {
+ renderErrorView: PropTypes.func.isRequired, // view to show if there's an error
+ renderLoadingView: PropTypes.func.isRequired, // loading indicator to show
+ url: PropTypes.string.isRequired,
+ automaticallyAdjustContentInsets: PropTypes.bool,
+ contentInset: EdgeInsetsPropType,
+ onNavigationStateChange: PropTypes.func,
+ startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
+ style: View.propTypes.style,
+ /**
+ * Used to locate this view in end-to-end tests.
+ */
+ testID: PropTypes.string,
+ },
+
+ getInitialState: function() {
+ return {
+ viewState: WebViewState.IDLE,
+ lastErrorEvent: null,
+ startInLoadingState: true,
+ };
+ },
+
+ componentWillMount: function() {
+ if (this.props.startInLoadingState) {
+ this.setState({viewState: WebViewState.LOADING});
+ }
+ },
+
+ render: function() {
+ var otherView = null;
+
+ if (this.state.viewState === WebViewState.LOADING) {
+ otherView = this.props.renderLoadingView();
+ } else if (this.state.viewState === WebViewState.ERROR) {
+ var errorEvent = this.state.lastErrorEvent;
+ otherView = this.props.renderErrorView(
+ errorEvent.domain,
+ errorEvent.code,
+ errorEvent.description);
+ } else if (this.state.viewState !== WebViewState.IDLE) {
+ console.error("RCTWebView invalid state encountered: " + this.state.loading);
+ }
+
+ var webViewStyles = [styles.container, this.props.style];
+ if (this.state.viewState === WebViewState.LOADING ||
+ this.state.viewState === WebViewState.ERROR) {
+ // if we're in either LOADING or ERROR states, don't show the webView
+ webViewStyles.push(styles.hidden);
+ }
+
+ var webView =
+ ;
+
+ return (
+
+ {webView}
+ {otherView}
+
+ );
+ },
+
+ goForward: function() {
+ RKUIManager.webViewGoForward(this.getWebWiewHandle());
+ },
+
+ goBack: function() {
+ RKUIManager.webViewGoBack(this.getWebWiewHandle());
+ },
+
+ reload: function() {
+ RKUIManager.webViewReload(this.getWebWiewHandle());
+ },
+
+ /**
+ * We return an event with a bunch of fields including:
+ * url, title, loading, canGoBack, canGoForward
+ */
+ updateNavigationState: function(event) {
+ if (this.props.onNavigationStateChange) {
+ this.props.onNavigationStateChange(event.nativeEvent);
+ }
+ },
+
+ getWebWiewHandle: function() {
+ return this.refs[RK_WEBVIEW_REF].getNodeHandle();
+ },
+
+ onLoadingStart: function(event) {
+ this.updateNavigationState(event);
+ },
+
+ onLoadingError: function(event) {
+ event.persist(); // persist this event because we need to store it
+ console.error("encountered an error loading page", event.nativeEvent);
+
+ this.setState({
+ lastErrorEvent: event.nativeEvent,
+ viewState: WebViewState.ERROR
+ });
+ },
+
+ onLoadingFinish: function(event) {
+ this.setState({
+ viewState: WebViewState.IDLE,
+ });
+ this.updateNavigationState(event);
+ },
+});
+
+var RCTWebView = createReactIOSNativeComponentClass({
+ validAttributes: merge(ReactIOSViewAttributes.UIView, {
+ url: true,
+ }),
+ uiViewClassName: 'RCTWebView',
+});
+
+var styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ hidden: {
+ height: 0,
+ flex: 0, // disable 'flex:1' when hiding a View
+ },
+});
+
+module.exports = WebView;
diff --git a/Libraries/Components/WebView/WebView.ios.js b/Libraries/Components/WebView/WebView.ios.js
new file mode 100644
index 000000000..ff916f8e7
--- /dev/null
+++ b/Libraries/Components/WebView/WebView.ios.js
@@ -0,0 +1,182 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule WebView
+ */
+'use strict';
+
+var EdgeInsetsPropType = require('EdgeInsetsPropType');
+var React = require('React');
+var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
+var StyleSheet = require('StyleSheet');
+var View = require('View');
+
+var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
+var keyMirror = require('keyMirror');
+var insetsDiffer = require('insetsDiffer');
+var merge = require('merge');
+
+var PropTypes = React.PropTypes;
+var { RKWebViewManager } = require('NativeModules');
+
+var RK_WEBVIEW_REF = 'webview';
+
+var WebViewState = keyMirror({
+ IDLE: null,
+ LOADING: null,
+ ERROR: null,
+});
+
+var NavigationType = {
+ click: RKWebViewManager.NavigationType.LinkClicked,
+ formsubmit: RKWebViewManager.NavigationType.FormSubmitted,
+ backforward: RKWebViewManager.NavigationType.BackForward,
+ reload: RKWebViewManager.NavigationType.Reload,
+ formresubmit: RKWebViewManager.NavigationType.FormResubmitted,
+ other: RKWebViewManager.NavigationType.Other,
+};
+
+var WebView = React.createClass({
+ statics: {
+ NavigationType: NavigationType,
+ },
+
+ propTypes: {
+ renderErrorView: PropTypes.func.isRequired, // view to show if there's an error
+ renderLoadingView: PropTypes.func.isRequired, // loading indicator to show
+ url: PropTypes.string.isRequired,
+ automaticallyAdjustContentInsets: PropTypes.bool,
+ shouldInjectAJAXHandler: PropTypes.bool,
+ contentInset: EdgeInsetsPropType,
+ onNavigationStateChange: PropTypes.func,
+ startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
+ style: View.propTypes.style,
+ },
+
+ getInitialState: function() {
+ return {
+ viewState: WebViewState.IDLE,
+ lastErrorEvent: null,
+ startInLoadingState: true,
+ };
+ },
+
+ componentWillMount: function() {
+ if (this.props.startInLoadingState) {
+ this.setState({viewState: WebViewState.LOADING});
+ }
+ },
+
+ render: function() {
+ var otherView = null;
+
+ if (this.state.viewState === WebViewState.LOADING) {
+ otherView = this.props.renderLoadingView();
+ } else if (this.state.viewState === WebViewState.ERROR) {
+ var errorEvent = this.state.lastErrorEvent;
+ otherView = this.props.renderErrorView(
+ errorEvent.domain,
+ errorEvent.code,
+ errorEvent.description);
+ } else if (this.state.viewState !== WebViewState.IDLE) {
+ console.error("RKWebView invalid state encountered: " + this.state.loading);
+ }
+
+ var webViewStyles = [styles.container, this.props.style];
+ if (this.state.viewState === WebViewState.LOADING ||
+ this.state.viewState === WebViewState.ERROR) {
+ // if we're in either LOADING or ERROR states, don't show the webView
+ webViewStyles.push(styles.hidden);
+ }
+
+ var webView =
+ ;
+
+ return (
+
+ {webView}
+ {otherView}
+
+ );
+ },
+
+ goForward: function() {
+ RKWebViewManager.goForward(this.getWebWiewHandle());
+ },
+
+ goBack: function() {
+ RKWebViewManager.goBack(this.getWebWiewHandle());
+ },
+
+ reload: function() {
+ RKWebViewManager.reload(this.getWebWiewHandle());
+ },
+
+ /**
+ * We return an event with a bunch of fields including:
+ * url, title, loading, canGoBack, canGoForward
+ */
+ updateNavigationState: function(event) {
+ if (this.props.onNavigationStateChange) {
+ this.props.onNavigationStateChange(event.nativeEvent);
+ }
+ },
+
+ getWebWiewHandle: function() {
+ return this.refs[RK_WEBVIEW_REF].getNodeHandle();
+ },
+
+ onLoadingStart: function(event) {
+ this.updateNavigationState(event);
+ },
+
+ onLoadingError: function(event) {
+ event.persist(); // persist this event because we need to store it
+ console.error("encountered an error loading page", event.nativeEvent);
+
+ this.setState({
+ lastErrorEvent: event.nativeEvent,
+ viewState: WebViewState.ERROR
+ });
+ },
+
+ onLoadingFinish: function(event) {
+ this.setState({
+ viewState: WebViewState.IDLE,
+ });
+ this.updateNavigationState(event);
+ },
+});
+
+var RCTWebView = createReactIOSNativeComponentClass({
+ validAttributes: merge(ReactIOSViewAttributes.UIView, {
+ url: true,
+ contentInset: {diff: insetsDiffer},
+ automaticallyAdjustContentInsets: true,
+ shouldInjectAJAXHandler: true
+ }),
+ uiViewClassName: 'RCTWebView',
+});
+
+var styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ hidden: {
+ height: 0,
+ flex: 0, // disable 'flex:1' when hiding a View
+ },
+});
+
+module.exports = WebView;
diff --git a/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..f377b4c98
--- /dev/null
+++ b/Libraries/RCTTest/RCTTest.xcodeproj/project.pbxproj
@@ -0,0 +1,264 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 585135371AB3C56F00882537 /* RCTTestModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 585135341AB3C56F00882537 /* RCTTestModule.m */; };
+ 585135381AB3C57000882537 /* RCTTestRunner.m in Sources */ = {isa = PBXBuildFile; fileRef = 585135361AB3C56F00882537 /* RCTTestRunner.m */; };
+ 585135391AB3C59A00882537 /* RCTTestRunner.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 585135351AB3C56F00882537 /* RCTTestRunner.h */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 580C376D1AB104AF0015E709 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "include/$(PRODUCT_NAME)";
+ dstSubfolderSpec = 16;
+ files = (
+ 585135391AB3C59A00882537 /* RCTTestRunner.h in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 580C376F1AB104AF0015E709 /* libRCTTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTTest.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 585135331AB3C56F00882537 /* RCTTestModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTestModule.h; sourceTree = ""; };
+ 585135341AB3C56F00882537 /* RCTTestModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTestModule.m; sourceTree = ""; };
+ 585135351AB3C56F00882537 /* RCTTestRunner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTestRunner.h; sourceTree = ""; };
+ 585135361AB3C56F00882537 /* RCTTestRunner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTestRunner.m; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 580C376C1AB104AF0015E709 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 580C37661AB104AF0015E709 = {
+ isa = PBXGroup;
+ children = (
+ 585135331AB3C56F00882537 /* RCTTestModule.h */,
+ 585135341AB3C56F00882537 /* RCTTestModule.m */,
+ 585135351AB3C56F00882537 /* RCTTestRunner.h */,
+ 585135361AB3C56F00882537 /* RCTTestRunner.m */,
+ 580C37701AB104AF0015E709 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 580C37701AB104AF0015E709 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 580C376F1AB104AF0015E709 /* libRCTTest.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 580C376E1AB104AF0015E709 /* RCTTest */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 580C37831AB104AF0015E709 /* Build configuration list for PBXNativeTarget "RCTTest" */;
+ buildPhases = (
+ 580C376B1AB104AF0015E709 /* Sources */,
+ 580C376C1AB104AF0015E709 /* Frameworks */,
+ 580C376D1AB104AF0015E709 /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = RCTTest;
+ productName = RCTTest;
+ productReference = 580C376F1AB104AF0015E709 /* libRCTTest.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 580C37671AB104AF0015E709 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0610;
+ ORGANIZATIONNAME = Facebook;
+ TargetAttributes = {
+ 580C376E1AB104AF0015E709 = {
+ CreatedOnToolsVersion = 6.1.1;
+ };
+ };
+ };
+ buildConfigurationList = 580C376A1AB104AF0015E709 /* Build configuration list for PBXProject "RCTTest" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 580C37661AB104AF0015E709;
+ productRefGroup = 580C37701AB104AF0015E709 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 580C376E1AB104AF0015E709 /* RCTTest */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 580C376B1AB104AF0015E709 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 585135371AB3C56F00882537 /* RCTTestModule.m in Sources */,
+ 585135381AB3C57000882537 /* RCTTestRunner.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 580C37811AB104AF0015E709 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../../ReactKit/**",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 580C37821AB104AF0015E709 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
+ "$(SRCROOT)/../../ReactKit/**",
+ );
+ IPHONEOS_DEPLOYMENT_TARGET = 8.1;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 580C37841AB104AF0015E709 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ OTHER_LDFLAGS = (
+ "-ObjC",
+ "-framework",
+ XCTest,
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ 580C37851AB104AF0015E709 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ OTHER_LDFLAGS = (
+ "-ObjC",
+ "-framework",
+ XCTest,
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 580C376A1AB104AF0015E709 /* Build configuration list for PBXProject "RCTTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 580C37811AB104AF0015E709 /* Debug */,
+ 580C37821AB104AF0015E709 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 580C37831AB104AF0015E709 /* Build configuration list for PBXNativeTarget "RCTTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 580C37841AB104AF0015E709 /* Debug */,
+ 580C37851AB104AF0015E709 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 580C37671AB104AF0015E709 /* Project object */;
+}
diff --git a/Libraries/RCTTest/RCTTestModule.h b/Libraries/RCTTest/RCTTestModule.h
new file mode 100644
index 000000000..f439df7b2
--- /dev/null
+++ b/Libraries/RCTTest/RCTTestModule.h
@@ -0,0 +1,9 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTBridgeModule.h"
+
+@interface RCTTestModule : NSObject
+
+@property (nonatomic, readonly, getter=isDone) BOOL done;
+
+@end
diff --git a/Libraries/RCTTest/RCTTestModule.m b/Libraries/RCTTest/RCTTestModule.m
new file mode 100644
index 000000000..eeb73734c
--- /dev/null
+++ b/Libraries/RCTTest/RCTTestModule.m
@@ -0,0 +1,14 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTTestModule.h"
+
+@implementation RCTTestModule
+
+- (void)markTestCompleted
+{
+ RCT_EXPORT();
+
+ _done = YES;
+}
+
+@end
diff --git a/Libraries/RCTTest/RCTTestRunner.h b/Libraries/RCTTest/RCTTestRunner.h
new file mode 100644
index 000000000..a774088dc
--- /dev/null
+++ b/Libraries/RCTTest/RCTTestRunner.h
@@ -0,0 +1,14 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import
+
+@interface RCTTestRunner : NSObject
+
+@property (nonatomic, copy) NSString *script;
+
+- (instancetype)initWithApp:(NSString *)app;
+- (void)runTest:(NSString *)moduleName;
+- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)expectErrorRegex;
+- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock;
+
+@end
diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m
new file mode 100644
index 000000000..3fec65b9e
--- /dev/null
+++ b/Libraries/RCTTest/RCTTestRunner.m
@@ -0,0 +1,64 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTTestRunner.h"
+
+#import "RCTRedBox.h"
+#import "RCTRootView.h"
+#import "RCTTestModule.h"
+#import "RCTUtils.h"
+
+#define TIMEOUT_SECONDS 30
+
+@implementation RCTTestRunner
+
+- (instancetype)initWithApp:(NSString *)app
+{
+ if (self = [super init]) {
+ _script = [NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app];
+ }
+ return self;
+}
+
+- (void)runTest:(NSString *)moduleName
+{
+ [self runTest:moduleName initialProps:nil expectErrorBlock:nil];
+}
+
+- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorRegex:(NSRegularExpression *)errorRegex
+{
+ [self runTest:moduleName initialProps:initialProps expectErrorBlock:^BOOL(NSString *error){
+ return [errorRegex numberOfMatchesInString:error options:0 range:NSMakeRange(0, [error length])] > 0;
+ }];
+}
+
+- (void)runTest:(NSString *)moduleName initialProps:(NSDictionary *)initialProps expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
+{
+ RCTTestModule *testModule = [[RCTTestModule alloc] init];
+ RCTRootView *rootView = [[RCTRootView alloc] init];
+ UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
+ vc.view = rootView;
+ rootView.moduleProvider = ^(void){
+ return @[testModule];
+ };
+ rootView.moduleName = moduleName;
+ rootView.initialProperties = initialProps;
+ rootView.scriptURL = [NSURL URLWithString:_script];
+
+ NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
+ NSString *error = [[RCTRedBox sharedInstance] currentErrorMessage];
+ while ([date timeIntervalSinceNow] > 0 && ![testModule isDone] && error == nil) {
+ [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
+ [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:date];
+ error = [[RCTRedBox sharedInstance] currentErrorMessage];
+ }
+ [[RCTRedBox sharedInstance] dismiss];
+ if (expectErrorBlock) {
+ RCTAssert(expectErrorBlock(error), @"Expected an error but got none.");
+ } else if (error) {
+ RCTAssert(error == nil, @"RedBox error: %@", error);
+ } else {
+ RCTAssert([testModule isDone], @"Test didn't finish within %d seconds", TIMEOUT_SECONDS);
+ }
+}
+
+@end
diff --git a/Libraries/Utilities/AlertIOS.js b/Libraries/Utilities/AlertIOS.js
new file mode 100644
index 000000000..ee0bb2d0b
--- /dev/null
+++ b/Libraries/Utilities/AlertIOS.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ *
+ * @providesModule AlertIOS
+ * @flow
+ */
+'use strict';
+
+var NativeModules = require('NativeModulesDeprecated');
+
+var RCTAlertManager = NativeModules.RCTAlertManager;
+
+var DEFAULT_BUTTON_TEXT = 'OK';
+var DEFAULT_BUTTON = {
+ text: DEFAULT_BUTTON_TEXT,
+ onPress: null,
+};
+
+/**
+ * AlertIOS manages native iOS alerts, option sheets, and share dialogs
+ */
+
+class AlertIOS {
+
+ /**
+ * Launches an alert dialog with the specified title and message.
+ *
+ * Optionally provide a list of buttons. Tapping any button will fire the
+ * respective onPress callback and dismiss the alert. By default, the only
+ * button will be an 'OK' button
+ *
+ * The last button in the list will be considered the 'Primary' button and
+ * it will appear bold.
+ *
+ * ```
+ * AlertIOS.alert(
+ * 'Foo Title',
+ * 'My Alert Msg',
+ * [
+ * {text: 'Foo', onPress: () => console.log('Foo Pressed!')},
+ * {text: 'Bar', onPress: () => console.log('Bar Pressed!')},
+ * ]
+ * )}
+ * ```
+ */
+ static alert(
+ title: ?string,
+ message: ?string,
+ buttons: ?Array<{
+ text: ?string;
+ onPress: ?Function;
+ }>
+ ): void {
+ var callbacks = [];
+ var buttonsSpec = [];
+ title = title || '';
+ message = message || '';
+ buttons = buttons || [DEFAULT_BUTTON];
+ buttons.forEach((btn, index) => {
+ callbacks[index] = btn.onPress;
+ var btnDef = {};
+ btnDef[index] = btn.text || DEFAULT_BUTTON_TEXT;
+ buttonsSpec.push(btnDef);
+ });
+ RCTAlertManager.alertWithArgs({
+ title,
+ message,
+ buttons: buttonsSpec,
+ }, (id) => {
+ var cb = callbacks[id];
+ cb && cb();
+ });
+ }
+
+}
+
+module.exports = AlertIOS;
diff --git a/Libraries/Utilities/differ/deepDiffer.js b/Libraries/Utilities/differ/deepDiffer.js
index 97c3f516f..e118097ab 100644
--- a/Libraries/Utilities/differ/deepDiffer.js
+++ b/Libraries/Utilities/differ/deepDiffer.js
@@ -38,7 +38,7 @@ var deepDiffer = function(one: any, two: any): bool {
for (var twoKey in two) {
// The only case we haven't checked yet is keys that are in two but aren't
// in one, which means they are different.
- if (one[twoKey] === undefined) {
+ if (one[twoKey] === undefined && two[twoKey] !== undefined) {
return true;
}
}
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index 48217795f..6f0b22df5 100644
--- a/Libraries/react-native/react-native.js
+++ b/Libraries/react-native/react-native.js
@@ -9,6 +9,7 @@ var ReactNative = {
...require('React'),
Animation: require('Animation'),
ActivityIndicatorIOS: require('ActivityIndicatorIOS'),
+ AlertIOS: require('AlertIOS'),
AppRegistry: require('AppRegistry'),
AppState: require('AppState'),
AppStateIOS: require('AppStateIOS'),
@@ -36,6 +37,7 @@ var ReactNative = {
TouchableOpacity: require('TouchableOpacity'),
TouchableWithoutFeedback: require('TouchableWithoutFeedback'),
View: require('View'),
+ WebView: require('WebView'),
invariant: require('invariant'),
ix: require('ix'),
};
diff --git a/ReactKit/Base/RCTRedBox.h b/ReactKit/Base/RCTRedBox.h
index 82137eb06..c13ac729b 100644
--- a/ReactKit/Base/RCTRedBox.h
+++ b/ReactKit/Base/RCTRedBox.h
@@ -11,6 +11,8 @@
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack;
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack;
+- (NSString *)currentErrorMessage;
+
- (void)dismiss;
@end
diff --git a/ReactKit/Base/RCTRedBox.m b/ReactKit/Base/RCTRedBox.m
index 93a73087e..1d037cd4f 100644
--- a/ReactKit/Base/RCTRedBox.m
+++ b/ReactKit/Base/RCTRedBox.m
@@ -6,6 +6,8 @@
@interface RCTRedBoxWindow : UIWindow
+@property (nonatomic, copy) NSString *lastErrorMessage;
+
@end
@implementation RCTRedBoxWindow
@@ -13,7 +15,6 @@
UIView *_rootView;
UITableView *_stackTraceTableView;
- NSString *_lastErrorMessage;
NSArray *_lastStackTrace;
UITableViewCell *_cachedMessageCell;
@@ -289,6 +290,15 @@
}
+- (NSString *)currentErrorMessage
+{
+ if (_window && !_window.hidden) {
+ return _window.lastErrorMessage;
+ } else {
+ return nil;
+ }
+}
+
- (void)dismiss
{
[_window dismiss];
diff --git a/ReactKit/Base/RCTRootView.h b/ReactKit/Base/RCTRootView.h
index 240c000c3..3fc0be165 100644
--- a/ReactKit/Base/RCTRootView.h
+++ b/ReactKit/Base/RCTRootView.h
@@ -2,6 +2,8 @@
#import
+#import "RCTBridge.h"
+
@interface RCTRootView : UIView
/**
@@ -19,13 +21,20 @@
*/
@property (nonatomic, copy) NSString *moduleName;
+/**
+ * A block that returns an array of pre-allocated modules. These
+ * modules will take precedence over any automatically registered
+ * modules of the same name.
+ */
+@property (nonatomic, copy) RCTBridgeModuleProviderBlock moduleProvider;
+
/**
* The default properties to apply to the view when the script bundle
* is first loaded. Defaults to nil/empty.
*/
@property (nonatomic, copy) NSDictionary *initialProperties;
-/**
+/**
* The class of the RCTJavaScriptExecutor to use with this view.
* If not specified, it will default to using RCTContextExecutor.
* Changes will take effect next time the bundle is reloaded.
diff --git a/ReactKit/Base/RCTRootView.m b/ReactKit/Base/RCTRootView.m
index 5f0698e32..c62793d50 100644
--- a/ReactKit/Base/RCTRootView.m
+++ b/ReactKit/Base/RCTRootView.m
@@ -142,7 +142,7 @@ static Class _globalExecutorClass;
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
- _bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:nil];
+ _bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:_moduleProvider];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler];
diff --git a/ReactKit/ReactKit.xcodeproj/project.pbxproj b/ReactKit/ReactKit.xcodeproj/project.pbxproj
index eed3a13e7..d00c6b2c8 100644
--- a/ReactKit/ReactKit.xcodeproj/project.pbxproj
+++ b/ReactKit/ReactKit.xcodeproj/project.pbxproj
@@ -31,6 +31,8 @@
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */; };
13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */; };
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; };
+ 13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; };
+ 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; };
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; };
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; };
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
@@ -124,6 +126,10 @@
13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIActivityIndicatorViewManager.m; sourceTree = ""; };
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; };
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; };
+ 13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = ""; };
+ 13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = ""; };
+ 13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = ""; };
+ 13C156041AB1A2840079392D /* RCTWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewManager.m; sourceTree = ""; };
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAutoInsetsProtocol.h; sourceTree = ""; };
13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollableProtocol.h; sourceTree = ""; };
13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewNodeProtocol.h; sourceTree = ""; };
@@ -299,6 +305,10 @@
13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */,
13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */,
13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */,
+ 13C156011AB1A2840079392D /* RCTWebView.h */,
+ 13C156021AB1A2840079392D /* RCTWebView.m */,
+ 13C156031AB1A2840079392D /* RCTWebViewManager.h */,
+ 13C156041AB1A2840079392D /* RCTWebViewManager.m */,
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */,
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */,
13E067531A70F44B002CDEE1 /* UIView+ReactKit.h */,
@@ -480,7 +490,9 @@
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
83C911101AAE6521001323A3 /* RCTAnimationManager.m in Sources */,
+ 13C156051AB1A2840079392D /* RCTWebView.m in Sources */,
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
+ 13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
diff --git a/ReactKit/Views/RCTScrollView.m b/ReactKit/Views/RCTScrollView.m
index 55b6690c9..fa6dc0f81 100644
--- a/ReactKit/Views/RCTScrollView.m
+++ b/ReactKit/Views/RCTScrollView.m
@@ -343,10 +343,14 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
- (void)setContentInset:(UIEdgeInsets)contentInset
{
+ CGPoint contentOffset = _scrollView.contentOffset;
+
_contentInset = contentInset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:NO];
+
+ _scrollView.contentOffset = contentOffset;
}
- (void)scrollToOffset:(CGPoint)offset
@@ -570,6 +574,39 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
}
}
+// Note: setting several properties of UIScrollView has the effect of
+// resetting its contentOffset to {0, 0}. To prevent this, we generate
+// setters here that will record the contentOffset beforehand, and
+// restore it after the property has been set.
+
+#define RCT_SET_AND_PRESERVE_OFFSET(setter, type) \
+- (void)setter:(type)value \
+{ \
+ CGPoint contentOffset = _scrollView.contentOffset; \
+ [_scrollView setter:value]; \
+ _scrollView.contentOffset = contentOffset; \
+}
+
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBounces, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, UIScrollViewKeyboardDismissMode)
+RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, CGFloat)
+RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, BOOL)
+RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, CGFloat);
+RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
+
+#pragma mark - Forward methods and properties to underlying UIScrollView
+
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [super respondsToSelector:aSelector] || [_scrollView respondsToSelector:aSelector];
@@ -577,13 +614,11 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
- // Pipe unrecognized properties to scrollview
[_scrollView setValue:value forKey:key];
}
- (id)valueForUndefinedKey:(NSString *)key
{
- // Pipe unrecognized properties from scrollview
return [_scrollView valueForKey:key];
}
diff --git a/ReactKit/Views/RCTScrollViewManager.m b/ReactKit/Views/RCTScrollViewManager.m
index 470c2c3f2..799d4b83c 100644
--- a/ReactKit/Views/RCTScrollViewManager.m
+++ b/ReactKit/Views/RCTScrollViewManager.m
@@ -34,10 +34,10 @@ RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices);
RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS);
-RCT_EXPORT_VIEW_PROPERTY(zoomScale); // TODO: this needs to be set first because it resets other props like contentOffset
+RCT_EXPORT_VIEW_PROPERTY(zoomScale);
RCT_EXPORT_VIEW_PROPERTY(contentInset);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets);
-RCT_EXPORT_VIEW_PROPERTY(contentOffset);
+RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset);
- (NSDictionary *)constantsToExport
{
diff --git a/ReactKit/Views/RCTWebView.h b/ReactKit/Views/RCTWebView.h
new file mode 100644
index 000000000..ec3c9d6eb
--- /dev/null
+++ b/ReactKit/Views/RCTWebView.h
@@ -0,0 +1,20 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTView.h"
+
+@class RCTEventDispatcher;
+
+@interface RCTWebView : RCTView
+
+@property (nonatomic, strong) NSURL *URL;
+@property (nonatomic, assign) UIEdgeInsets contentInset;
+@property (nonatomic, assign) BOOL shouldInjectAJAXHandler;
+@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
+
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
+
+- (void)goForward;
+- (void)goBack;
+- (void)reload;
+
+@end
diff --git a/ReactKit/Views/RCTWebView.m b/ReactKit/Views/RCTWebView.m
new file mode 100644
index 000000000..cc5a07578
--- /dev/null
+++ b/ReactKit/Views/RCTWebView.m
@@ -0,0 +1,180 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTWebView.h"
+
+#import
+
+#import "RCTAutoInsetsProtocol.h"
+#import "RCTEventDispatcher.h"
+#import "RCTLog.h"
+#import "RCTUtils.h"
+#import "RCTView.h"
+#import "UIView+ReactKit.h"
+
+@interface RCTWebView ()
+
+@end
+
+@implementation RCTWebView
+{
+ RCTEventDispatcher *_eventDispatcher;
+ UIWebView *_webView;
+}
+
+- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
+{
+ if ((self = [super initWithFrame:CGRectZero])) {
+ _automaticallyAdjustContentInsets = YES;
+ _contentInset = UIEdgeInsetsZero;
+ _eventDispatcher = eventDispatcher;
+ _webView = [[UIWebView alloc] initWithFrame:self.bounds];
+ _webView.delegate = self;
+ [self addSubview:_webView];
+ }
+ return self;
+}
+
+- (void)goForward
+{
+ [_webView goForward];
+}
+
+- (void)goBack
+{
+ [_webView goBack];
+}
+
+- (void)reload
+{
+ [_webView reload];
+}
+
+- (void)setURL:(NSURL *)URL
+{
+ // Because of the way React works, as pages redirect, we actually end up
+ // passing the redirect urls back here, so we ignore them if trying to load
+ // the same url. We'll expose a call to 'reload' to allow a user to load
+ // the existing page.
+ if ([URL isEqual:_webView.request.URL]) {
+ return;
+ }
+ if (!URL) {
+ // Clear the webview
+ [_webView loadHTMLString:nil baseURL:nil];
+ return;
+ }
+ [_webView loadRequest:[NSURLRequest requestWithURL:URL]];
+}
+
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+ _webView.frame = self.bounds;
+ [RCTView autoAdjustInsetsForView:self
+ withScrollView:_webView.scrollView
+ updateOffset:YES];
+}
+
+- (void)setContentInset:(UIEdgeInsets)contentInset
+{
+ _contentInset = contentInset;
+ [RCTView autoAdjustInsetsForView:self
+ withScrollView:_webView.scrollView
+ updateOffset:NO];
+}
+
+- (NSMutableDictionary *)baseEvent
+{
+ NSURL *url = _webView.request.URL;
+ NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"];
+ NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{
+ @"target": self.reactTag,
+ @"url": url ? [url absoluteString] : @"",
+ @"loading" : @(_webView.loading),
+ @"title": title,
+ @"canGoBack": @([_webView canGoBack]),
+ @"canGoForward" : @([_webView canGoForward]),
+ }];
+
+ return event;
+}
+
+#pragma mark - UIWebViewDelegate methods
+
+static NSString *const RCTJSAJAXScheme = @"react-ajax";
+
+- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
+ navigationType:(UIWebViewNavigationType)navigationType
+{
+ // We have this check to filter out iframe requests and whatnot
+ BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
+ if (isTopFrame) {
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary: @{
+ @"url": [request.URL absoluteString],
+ @"navigationType": @(navigationType)
+ }];
+ [_eventDispatcher sendInputEventWithName:@"topLoadingStart" body:event];
+ }
+
+ // AJAX handler
+ return ![request.URL.scheme isEqualToString:RCTJSAJAXScheme];
+}
+
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
+{
+ if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
+ // NSURLErrorCancelled is reported when a page has a redirect OR if you load
+ // a new URL in the WebView before the previous one came back. We can just
+ // ignore these since they aren't real errors.
+ // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
+ return;
+ }
+
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary: @{
+ @"domain": error.domain,
+ @"code": @(error.code),
+ @"description": [error localizedDescription],
+ }];
+ [_eventDispatcher sendInputEventWithName:@"topLoadingError" body:event];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView *)webView
+{
+ if (_shouldInjectAJAXHandler) {
+
+ // From http://stackoverflow.com/questions/5353278/uiwebviewdelegate-not-monitoring-xmlhttprequest
+
+ [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"\
+ var s_ajaxListener = new Object(); \n\
+ s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open; \n\
+ s_ajaxListener.tempSend = XMLHttpRequest.prototype.send; \n\
+ s_ajaxListener.callback = function() { \n\
+ window.location.href = '%@://' + this.url; \n\
+ } \n\
+ XMLHttpRequest.prototype.open = function(a,b) { \n\
+ s_ajaxListener.tempOpen.apply(this, arguments); \n\
+ s_ajaxListener.method = a; \n\
+ s_ajaxListener.url = b; \n\
+ if (a.toLowerCase() === 'get') { \n\
+ s_ajaxListener.data = (b.split('?'))[1]; \n\
+ } \n\
+ } \n\
+ XMLHttpRequest.prototype.send = function(a,b) { \n\
+ s_ajaxListener.tempSend.apply(this, arguments); \n\
+ if (s_ajaxListener.method.toLowerCase() === 'post') { \n\
+ s_ajaxListener.data = a; \n\
+ } \n\
+ s_ajaxListener.callback(); \n\
+ } \n\
+ ", RCTJSAJAXScheme]];
+ }
+
+ // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
+ if (!webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
+ [_eventDispatcher sendInputEventWithName:@"topLoadingFinish" body:[self baseEvent]];
+ }
+}
+
+@end
diff --git a/ReactKit/Views/RCTWebViewManager.h b/ReactKit/Views/RCTWebViewManager.h
new file mode 100644
index 000000000..d375cbdab
--- /dev/null
+++ b/ReactKit/Views/RCTWebViewManager.h
@@ -0,0 +1,7 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTViewManager.h"
+
+@interface RCTWebViewManager : RCTViewManager
+
+@end
diff --git a/ReactKit/Views/RCTWebViewManager.m b/ReactKit/Views/RCTWebViewManager.m
new file mode 100644
index 000000000..8e68d9eb8
--- /dev/null
+++ b/ReactKit/Views/RCTWebViewManager.m
@@ -0,0 +1,76 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#import "RCTWebViewManager.h"
+
+#import "RCTBridge.h"
+#import "RCTSparseArray.h"
+#import "RCTUIManager.h"
+#import "RCTWebView.h"
+
+@implementation RCTWebViewManager
+
+- (UIView *)view
+{
+ return [[RCTWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
+}
+
+RCT_REMAP_VIEW_PROPERTY(url, URL);
+RCT_EXPORT_VIEW_PROPERTY(contentInset);
+RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets);
+RCT_EXPORT_VIEW_PROPERTY(shouldInjectAJAXHandler);
+
+- (NSDictionary *)constantsToExport
+{
+ return @{
+ @"NavigationType": @{
+ @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked),
+ @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted),
+ @"BackForward": @(UIWebViewNavigationTypeBackForward),
+ @"Reload": @(UIWebViewNavigationTypeReload),
+ @"FormResubmitted": @(UIWebViewNavigationTypeFormResubmitted),
+ @"Other": @(UIWebViewNavigationTypeOther)
+ },
+ };
+}
+
+- (void)goBack:(NSNumber *)reactTag
+{
+ RCT_EXPORT();
+
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ RCTWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RKWebView, got: %@", view);
+ }
+ [view goBack];
+ }];
+}
+
+- (void)goForward:(NSNumber *)reactTag
+{
+ RCT_EXPORT();
+
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ id view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RKWebView, got: %@", view);
+ }
+ [view goForward];
+ }];
+}
+
+
+- (void)reload:(NSNumber *)reactTag
+{
+ RCT_EXPORT();
+
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
+ RCTWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWebView class]]) {
+ RCTLogMustFix(@"Invalid view returned from registry, expecting RKWebView, got: %@", view);
+ }
+ [view reload];
+ }];
+}
+
+@end
diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js
index f1a30545c..df29e57c5 100644
--- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js
+++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js
@@ -22,6 +22,8 @@ function ModuleDescriptor(fields) {
this.isPolyfill = fields.isPolyfill || false;
+ this.isAsset = fields.isAsset || false;
+
this._fields = fields;
}
diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js
index eb839296c..6dee93ec6 100644
--- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js
+++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js
@@ -56,6 +56,40 @@ describe('DependencyGraph', function() {
});
});
+ pit('should get dependencies', function() {
+ var root = '/root';
+ fs.__setMockFilesystem({
+ 'root': {
+ 'index.js': [
+ '/**',
+ ' * @providesModule index',
+ ' */',
+ 'require("image!a")'
+ ].join('\n'),
+ 'imgs': {
+ 'a.png': ''
+ },
+ }
+ });
+
+ var dgraph = new DependencyGraph({
+ roots: [root],
+ fileWatcher: fileWatcher,
+ assetRoots: ['/root/imgs']
+ });
+ return dgraph.load().then(function() {
+ expect(dgraph.getOrderedDependencies('/root/index.js'))
+ .toEqual([
+ {id: 'index', path: '/root/index.js', dependencies: ['image!a']},
+ { id: 'image!a',
+ path: '/root/imgs/a.png',
+ dependencies: [],
+ isAsset: true
+ },
+ ]);
+ });
+ });
+
pit('should get recursive dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
index ce6318562..122701d56 100644
--- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
+++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
@@ -28,12 +28,22 @@ var validateOpts = declareOpts({
type: 'object',
required: true,
},
+ assetRoots: {
+ type: 'array',
+ default: [],
+ },
+ assetExts: {
+ type: 'array',
+ default: ['png'],
+ }
});
function DependecyGraph(options) {
var opts = validateOpts(options);
this._roots = opts.roots;
+ this._assetRoots = opts.assetRoots;
+ this._assetExts = opts.assetExts;
this._ignoreFilePath = opts.ignoreFilePath;
this._fileWatcher = options.fileWatcher;
@@ -50,7 +60,16 @@ function DependecyGraph(options) {
}
DependecyGraph.prototype.load = function() {
- return this._loading || (this._loading = this._search());
+ if (this._loading != null) {
+ return this._loading;
+ }
+
+ this._loading = q.all([
+ this._search(),
+ this._buildAssetMap(),
+ ]);
+
+ return this._loading;
};
/**
@@ -115,6 +134,15 @@ DependecyGraph.prototype.resolveDependency = function(
fromModule,
depModuleId
) {
+ // Process asset requires.
+ var assetMatch = depModuleId.match(/^image!(.+)/);
+ if (assetMatch && assetMatch[1]) {
+ if (!this._assetMap[assetMatch[1]]) {
+ throw new Error('Cannot find asset: ' + assetMatch[1]);
+ }
+ return this._assetMap[assetMatch[1]];
+ }
+
var packageJson, modulePath, dep;
// Package relative modules starts with '.' or '..'.
@@ -214,32 +242,13 @@ DependecyGraph.prototype._search = function() {
// 2. Filter the files and queue up the directories.
// 3. Process any package.json in the files
// 4. recur.
- return readDir(dir)
- .then(function(files){
- return q.all(files.map(function(filePath) {
- return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
- }));
- })
- .then(function(filePaths) {
- filePaths = filePaths.filter(function(filePath) {
- if (filePath == null) {
+ return readAndStatDir(dir)
+ .spread(function(files, stats) {
+ var modulePaths = files.filter(function(filePath, i) {
+ if (self._ignoreFilePath(filePath)) {
return false;
}
- return !self._ignoreFilePath(filePath);
- });
-
- var statsP = filePaths.map(function(filePath) {
- return lstat(filePath).catch(handleBrokenLink);
- });
-
- return [
- filePaths,
- q.all(statsP)
- ];
- })
- .spread(function(files, stats) {
- var modulePaths = files.filter(function(filePath, i) {
if (stats[i].isDirectory()) {
self._queue.push(filePath);
return false;
@@ -465,6 +474,19 @@ DependecyGraph.prototype._getAbsolutePath = function(filePath) {
return null;
};
+DependecyGraph.prototype._buildAssetMap = function() {
+ if (this._assetRoots == null || this._assetRoots.length === 0) {
+ return q();
+ }
+
+ var self = this;
+ return buildAssetMap(this._assetRoots, this._assetExts)
+ .then(function(map) {
+ self._assetMap = map;
+ return map;
+ });
+};
+
/**
* Extract all required modules from a `code` string.
*/
@@ -511,4 +533,70 @@ function handleBrokenLink(e) {
return q();
}
+function readAndStatDir(dir) {
+ return readDir(dir)
+ .then(function(files){
+ return q.all(files.map(function(filePath) {
+ return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
+ }));
+ }).then(function(files) {
+ files = files.filter(function(f) {
+ return !!f;
+ });
+
+ var stats = files.map(function(filePath) {
+ return lstat(filePath).catch(handleBrokenLink);
+ });
+
+ return [
+ files,
+ q.all(stats),
+ ];
+ });
+}
+
+/**
+ * Given a list of roots and list of extensions find all the files in
+ * the directory with that extension and build a map of those assets.
+ */
+function buildAssetMap(roots, exts) {
+ var queue = roots.slice(0);
+ var map = Object.create(null);
+
+ function search() {
+ var root = queue.shift();
+
+ if (root == null) {
+ return q(map);
+ }
+
+ return readAndStatDir(root).spread(function(files, stats) {
+ files.forEach(function(file, i) {
+ if (stats[i].isDirectory()) {
+ queue.push(file);
+ } else {
+ var ext = path.extname(file).replace(/^\./, '');
+ if (exts.indexOf(ext) !== -1) {
+ var assetName = path.basename(file, '.' + ext);
+ if (map[assetName] != null) {
+ debug('Conflcting assets', assetName);
+ }
+
+ map[assetName] = new ModuleDescriptor({
+ id: 'image!' + assetName,
+ path: path.resolve(file),
+ isAsset: true,
+ dependencies: [],
+ });
+ }
+ }
+ });
+
+ return search();
+ });
+ }
+
+ return search();
+}
+
module.exports = DependecyGraph;
diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js
index 6aada00b9..fdc779edc 100644
--- a/packager/react-packager/src/DependencyResolver/haste/index.js
+++ b/packager/react-packager/src/DependencyResolver/haste/index.js
@@ -17,7 +17,6 @@ var DEFINE_MODULE_CODE = [
].join('');
var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
-
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
var validateOpts = declareOpts({
@@ -40,6 +39,10 @@ var validateOpts = declareOpts({
type: 'string',
default: 'haste',
},
+ assetRoots: {
+ type: 'array',
+ default: [],
+ },
});
function HasteDependencyResolver(options) {
@@ -51,11 +54,12 @@ function HasteDependencyResolver(options) {
this._depGraph = new DependencyGraph({
roots: opts.projectRoots,
+ assetRoots: opts.assetRoots,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(opts.blacklistRE && opts.blacklistRE.test(filepath));
},
- fileWatcher: this._fileWatcher
+ fileWatcher: this._fileWatcher,
});
diff --git a/packager/react-packager/src/Packager/__tests__/Packager-test.js b/packager/react-packager/src/Packager/__tests__/Packager-test.js
index d8348be8f..498faea3a 100644
--- a/packager/react-packager/src/Packager/__tests__/Packager-test.js
+++ b/packager/react-packager/src/Packager/__tests__/Packager-test.js
@@ -40,6 +40,11 @@ describe('Packager', function() {
var modules = [
{id: 'foo', path: '/root/foo.js', dependencies: []},
{id: 'bar', path: '/root/bar.js', dependencies: []},
+ { id: 'image!img',
+ path: '/root/img/img.png',
+ isAsset: true,
+ dependencies: [],
+ }
];
getDependencies.mockImpl(function() {
@@ -74,6 +79,15 @@ describe('Packager', function() {
'source /root/bar.js',
'/root/bar.js'
]);
+ expect(p.addModule.mock.calls[2]).toEqual([
+ 'lol module.exports = ' +
+ JSON.stringify({ uri: 'img', isStatic: true}) +
+ '; lol',
+ 'module.exports = ' +
+ JSON.stringify({ uri: 'img', isStatic: true}) +
+ ';',
+ '/root/img/img.png'
+ ]);
expect(p.finalize.mock.calls[0]).toEqual([
{runMainModule: true}
diff --git a/packager/react-packager/src/Packager/index.js b/packager/react-packager/src/Packager/index.js
index 75cccdb26..c21889b48 100644
--- a/packager/react-packager/src/Packager/index.js
+++ b/packager/react-packager/src/Packager/index.js
@@ -44,6 +44,10 @@ var validateOpts = declareOpts({
type: 'boolean',
default: false,
},
+ assetRoots: {
+ type: 'array',
+ required: false,
+ },
});
function Packager(options) {
@@ -56,7 +60,8 @@ function Packager(options) {
blacklistRE: opts.blacklistRE,
polyfillModuleNames: opts.polyfillModuleNames,
nonPersistent: opts.nonPersistent,
- moduleFormat: opts.moduleFormat
+ moduleFormat: opts.moduleFormat,
+ assetRoots: opts.assetRoots,
});
this._transformer = new Transformer({
@@ -118,10 +123,18 @@ Packager.prototype.getDependencies = function(main, isDev) {
};
Packager.prototype._transformModule = function(module) {
+ var transform;
+
+ if (module.isAsset) {
+ transform = q(generateAssetModule(module));
+ } else {
+ transform = this._transformer.loadFileAndTransform(
+ path.resolve(module.path)
+ );
+ }
+
var resolver = this._resolver;
- return this._transformer.loadFileAndTransform(
- path.resolve(module.path)
- ).then(function(transformed) {
+ return transform.then(function(transformed) {
return _.extend(
{},
transformed,
@@ -140,5 +153,17 @@ Packager.prototype.getGraphDebugInfo = function() {
return this._resolver.getDebugInfo();
};
+function generateAssetModule(module) {
+ var code = 'module.exports = ' + JSON.stringify({
+ uri: module.id.replace(/^[^!]+!/, ''),
+ isStatic: true,
+ }) + ';';
+
+ return {
+ code: code,
+ sourceCode: code,
+ sourcePath: module.path,
+ };
+}
module.exports = Packager;
diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js
index 8982bc916..7df686ad2 100644
--- a/packager/react-packager/src/Server/index.js
+++ b/packager/react-packager/src/Server/index.js
@@ -43,6 +43,10 @@ var validateOpts = declareOpts({
type: 'boolean',
default: false,
},
+ assetRoots: {
+ type: 'array',
+ required: false,
+ },
});
function Server(options) {