diff --git a/Examples/2048/Game2048.js b/Examples/2048/Game2048.js index 4337aee91d4d75..e6b7fa6d4c194a 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 a43b5573894c77..3c8987ce3f2535 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 00000000000000..a2b92d9759048b --- /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 59653055e124c1..b7108681e930c6 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 00000000000000..4b7a1513d86875 --- /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 00000000000000..f0ec66bdb5b297 --- /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 00000000000000..db988faf8615d4 --- /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 00000000000000..52cc0828d826d5 --- /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 00000000000000..413d60e76d07bf --- /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 00000000000000..08a42699daa9ca 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 00000000000000..08a42699daa9ca 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 00000000000000..08a42699daa9ca 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 00000000000000..08a42699daa9ca 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 00000000000000..08a42699daa9ca 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 00000000000000..08a42699daa9ca 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 00000000000000..245054621e525e --- /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 00000000000000..5b6e78838fc28f --- /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 00000000000000..c850360b119f75 --- /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 00000000000000..ee60aa38872ce2 --- /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 00000000000000..87e3a61754c916 --- /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 00000000000000..9d9020f7d1188c --- /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 00000000000000..357a233b128ce8 --- /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 80029f1485a11f..30624266d8f3e9 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 ff29e273535144..56f63fb8ef4d9d 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 00000000000000..8190258aee9462 --- /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 00000000000000..ff916f8e7ea6f7 --- /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 00000000000000..f377b4c9823e1f --- /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 00000000000000..f439df7b2301ae --- /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 00000000000000..eeb73734c817e4 --- /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 00000000000000..a774088dc64d02 --- /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 00000000000000..3fec65b9e9a785 --- /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 00000000000000..ee0bb2d0b2eff7 --- /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 97c3f516fe05bf..e118097ab01f06 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 48217795fb1a6d..6f0b22df5f7152 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 82137eb0683b1a..c13ac729b2ec24 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 93a73087e9fb09..1d037cd4f843b3 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 @@ @implementation RCTRedBoxWindow UIView *_rootView; UITableView *_stackTraceTableView; - NSString *_lastErrorMessage; NSArray *_lastStackTrace; UITableViewCell *_cachedMessageCell; @@ -289,6 +290,15 @@ - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHi } +- (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 240c000c376288..3fc0be165074bf 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 5f0698e324edd9..c62793d503c240 100644 --- a/ReactKit/Base/RCTRootView.m +++ b/ReactKit/Base/RCTRootView.m @@ -142,7 +142,7 @@ - (void)loadBundle // 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 eed3a13e743257..d00c6b2c880fe1 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 55b6690c9da052..fa6dc0f81aa225 100644 --- a/ReactKit/Views/RCTScrollView.m +++ b/ReactKit/Views/RCTScrollView.m @@ -343,10 +343,14 @@ - (void)layoutSubviews - (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 @@ - (void)reactBridgeDidFinishTransaction } } +// 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 @@ - (BOOL)respondsToSelector:(SEL)aSelector - (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 470c2c3f202919..799d4b83c9dba2 100644 --- a/ReactKit/Views/RCTScrollViewManager.m +++ b/ReactKit/Views/RCTScrollViewManager.m @@ -34,10 +34,10 @@ - (UIView *)view 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 00000000000000..ec3c9d6eb159cf --- /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 00000000000000..cc5a0757886fb0 --- /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 00000000000000..d375cbdab69c8f --- /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 00000000000000..8e68d9eb8af517 --- /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 f1a30545c94e98..df29e57c54d797 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 eb839296c6724b..6dee93ec636687 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 ce63185629ce19..122701d56d976a 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 6aada00b98ee94..fdc779edc92f9e 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 d8348be8fa3074..498faea3acad1b 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 75cccdb26d040b..c21889b48e36bd 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 8982bc9165f78b..7df686ad28fc22 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) {