From e1454c29c5b28e8f7b07124a5f10a3e020463a49 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Tue, 26 May 2015 16:58:16 -0100 Subject: [PATCH 01/25] [ReactNative] Run UIExplorer tests on sandcastle --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ++++ Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index d72262e78..9d3adb2ee 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,6 +50,10 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#if RUNNING_ON_CI + jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 245054621..349bd9a28 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.internal.uiexplorer.local CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSLocationWhenInUseUsageDescription + You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -34,8 +36,6 @@ 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/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index aa524ef56..7ff8c6379 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock block in blocks) { - block(cached, data, error); + for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { + cacheDownloadBlock(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9c0cacf70..8a7e739bb 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,7 +35,11 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; +#if RUNNING_ON_CI + _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; +#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; +#endif } return self; } From e68f89bfadba2381e7ea6c958a5aa187bc862713 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Wed, 3 Jun 2015 09:49:22 -0700 Subject: [PATCH 02/25] Added ProgressViewIOS --- Examples/UIExplorer/ProgressViewIOSExample.js | 83 +++++++++++++++++ Examples/UIExplorer/UIExplorerList.js | 1 + .../ProgressViewIOS.android.js | 49 ++++++++++ .../ProgressViewIOS/ProgressViewIOS.ios.js | 89 +++++++++++++++++++ Libraries/react-native/react-native.js | 9 +- React/React.xcodeproj/project.pbxproj | 6 ++ React/Views/RCTProgressViewManager.h | 14 +++ React/Views/RCTProgressViewManager.m | 47 ++++++++++ 8 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 Examples/UIExplorer/ProgressViewIOSExample.js create mode 100644 Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js create mode 100644 Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js create mode 100644 React/Views/RCTProgressViewManager.h create mode 100644 React/Views/RCTProgressViewManager.m diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/ProgressViewIOSExample.js new file mode 100644 index 000000000..f0a17a7c6 --- /dev/null +++ b/Examples/UIExplorer/ProgressViewIOSExample.js @@ -0,0 +1,83 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react-native'); +var { + ProgressViewIOS, + StyleSheet, + View, +} = React; +var TimerMixin = require('react-timer-mixin'); + +var ProgressViewExample = React.createClass({ + mixins: [TimerMixin], + + getInitialState() { + return { + progress: 0, + }; + }, + + componentDidMount() { + this.updateProgress(); + }, + + updateProgress() { + var progress = this.state.progress + 0.01; + this.setState({ progress }); + this.requestAnimationFrame(() => this.updateProgress()); + }, + + getProgress(offset) { + var progress = this.state.progress + offset; + return Math.sin(progress % Math.PI) % 1; + }, + + render() { + return ( + + + + + + + + ); + }, +}); + +exports.framework = 'React'; +exports.title = 'ProgressViewIOS'; +exports.description = 'ProgressViewIOS'; +exports.examples = [{ + title: 'ProgressViewIOS', + render() { + return ( + + ); + } +}]; + +var styles = StyleSheet.create({ + container: { + marginTop: -20, + backgroundColor: 'transparent', + }, + progressView: { + marginTop: 20, + } +}); diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index a030220ca..df9a3b123 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -45,6 +45,7 @@ var COMPONENTS = [ require('./NavigatorIOSColorsExample'), require('./NavigatorIOSExample'), require('./PickerIOSExample'), + require('./ProgressViewIOSExample'), require('./ScrollViewExample'), require('./SegmentedControlIOSExample'), require('./SliderIOSExample'), diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js new file mode 100644 index 000000000..b9103c667 --- /dev/null +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js @@ -0,0 +1,49 @@ + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SegmentedControlIOS + */ + +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +var Dummy = React.createClass({ + render: function() { + return ( + + + ProgressViewIOS is not supported on this platform! + + + ); + }, +}); + +var styles = StyleSheet.create({ + dummy: { + width: 120, + height: 20, + backgroundColor: '#ffbcbc', + borderWidth: 1, + borderColor: 'red', + alignItems: 'center', + justifyContent: 'center', + }, + text: { + color: '#333333', + margin: 5, + fontSize: 10, + } +}); + +module.exports = Dummy; diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js new file mode 100644 index 000000000..b4fb3e768 --- /dev/null +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ProgressViewIOS + * @flow + */ +'use strict'; + +var Image = require('Image'); +var NativeMethodsMixin = require('NativeMethodsMixin'); +var NativeModules = require('NativeModules'); +var PropTypes = require('ReactPropTypes'); +var React = require('React'); +var StyleSheet = require('StyleSheet'); + +var requireNativeComponent = require('requireNativeComponent'); +var verifyPropTypes = require('verifyPropTypes'); + +/** + * Use `ProgressViewIOS` to render a UIProgressView on iOS. + */ +var ProgressViewIOS = React.createClass({ + mixins: [NativeMethodsMixin], + + propTypes: { + /** + * The progress bar style. + */ + progressViewStyle: PropTypes.oneOf(['default', 'bar']), + + /** + * The progress value (between 0 and 1). + */ + progress: PropTypes.number, + + /** + * The tint color of the progress bar itself. + */ + progressTintColor: PropTypes.string, + + /** + * The tint color of the progress bar track. + */ + trackTintColor: PropTypes.string, + + /** + * A stretchable image to display as the progress bar. + */ + progressImage: Image.propTypes.source, + + /** + * A stretchable image to display behind the progress bar. + */ + trackImage: Image.propTypes.source, + }, + + render: function() { + return ( + + ); + } +}); + +var styles = StyleSheet.create({ + progressView: { + height: NativeModules.ProgressViewManager.ComponentHeight + }, +}); + +var RCTProgressView = requireNativeComponent( + 'RCTProgressView', + null +); +if (__DEV__) { + verifyPropTypes( + RCTProgressView, + RCTProgressView.viewConfig + ); +} + +module.exports = ProgressViewIOS; diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index c81183b5b..6b1c992cc 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -24,11 +24,12 @@ var ReactNative = Object.assign(Object.create(require('React')), { Image: require('Image'), ListView: require('ListView'), MapView: require('MapView'), + Navigator: require('Navigator'), NavigatorIOS: require('NavigatorIOS'), PickerIOS: require('PickerIOS'), - Navigator: require('Navigator'), - SegmentedControlIOS: require('SegmentedControlIOS'), + ProgressViewIOS: require('ProgressViewIOS'), ScrollView: require('ScrollView'), + SegmentedControlIOS: require('SegmentedControlIOS'), SliderIOS: require('SliderIOS'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), @@ -47,12 +48,12 @@ var ReactNative = Object.assign(Object.create(require('React')), { AsyncStorage: require('AsyncStorage'), CameraRoll: require('CameraRoll'), InteractionManager: require('InteractionManager'), - LinkingIOS: require('LinkingIOS'), LayoutAnimation: require('LayoutAnimation'), + LinkingIOS: require('LinkingIOS'), NetInfo: require('NetInfo'), + PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), - PanResponder: require('PanResponder'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index c7309989b..3c0cfe722 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; }; 134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; }; 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; }; + 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */; }; 13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; }; 1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; }; 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; }; @@ -104,6 +105,8 @@ 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = ""; }; 134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = ""; }; 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = ""; }; + 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTProgressViewManager.h; sourceTree = ""; }; + 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTProgressViewManager.m; sourceTree = ""; }; 13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = ""; }; 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = ""; }; 1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = ""; }; @@ -307,6 +310,8 @@ 58114A141AAE854800E7D092 /* RCTPickerManager.h */, 58114A151AAE854800E7D092 /* RCTPickerManager.m */, 13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */, + 13513F3A1B1F43F400FCE529 /* RCTProgressViewManager.h */, + 13513F3B1B1F43F400FCE529 /* RCTProgressViewManager.m */, 131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */, 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */, 131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */, @@ -524,6 +529,7 @@ 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */, 832348161A77A5AA00B55238 /* Layout.c in Sources */, 14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */, + 13513F3C1B1F43F400FCE529 /* RCTProgressViewManager.m in Sources */, 14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */, 14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */, 13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */, diff --git a/React/Views/RCTProgressViewManager.h b/React/Views/RCTProgressViewManager.h new file mode 100644 index 000000000..ae8a6a388 --- /dev/null +++ b/React/Views/RCTProgressViewManager.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewManager.h" + +@interface RCTProgressViewManager : RCTViewManager + +@end diff --git a/React/Views/RCTProgressViewManager.m b/React/Views/RCTProgressViewManager.m new file mode 100644 index 000000000..deb6285a6 --- /dev/null +++ b/React/Views/RCTProgressViewManager.m @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTProgressViewManager.h" + +#import "RCTConvert.h" + +@implementation RCTConvert (RCTProgressViewManager) + +RCT_ENUM_CONVERTER(UIProgressViewStyle, (@{ + @"default": @(UIProgressViewStyleDefault), + @"bar": @(UIProgressViewStyleBar), +}), UIProgressViewStyleDefault, integerValue) + +@end + +@implementation RCTProgressViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[UIProgressView alloc] init]; +} + +RCT_EXPORT_VIEW_PROPERTY(progressViewStyle, UIProgressViewStyle) +RCT_EXPORT_VIEW_PROPERTY(progress, float) +RCT_EXPORT_VIEW_PROPERTY(progressTintColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(trackTintColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(progressImage, UIImage) +RCT_EXPORT_VIEW_PROPERTY(trackImage, UIImage) + +- (NSDictionary *)constantsToExport +{ + UIProgressView *view = [[UIProgressView alloc] init]; + return @{ + @"ComponentHeight": @(view.intrinsicContentSize.height), + }; +} + +@end From bba576a1728b1d228bf3daa26bef5b96c062aeaa Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 10:25:53 -0700 Subject: [PATCH 03/25] press ctrl-i to toggle inspect element --- React/Base/RCTDevMenu.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 2416e974c..18de3a457 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -110,6 +110,13 @@ RCT_EXPORT_MODULE() [weakSelf toggle]; }]; + // Toggle element inspector + [commands registerKeyCommandWithInput:@"i" + modifierFlags:UIKeyModifierCommand + action:^(UIKeyCommand *command) { + [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; + }]; + // Reload in normal mode [commands registerKeyCommandWithInput:@"n" modifierFlags:UIKeyModifierCommand From ca7a764c8c76b8f0c8eb3ff19835f563854a8dac Mon Sep 17 00:00:00 2001 From: Tyler McGinnis Date: Wed, 3 Jun 2015 11:35:32 -0700 Subject: [PATCH 04/25] [Cosmetic] Fix typo in packager README Summary: Closes https://github.com/facebook/react-native/pull/693 Github Author: Tyler McGinnis Test Plan: Imported from GitHub, without a `Test Plan:` line. --- packager/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packager/README.md b/packager/README.md index 8f9f649bf..c3fae4806 100644 --- a/packager/README.md +++ b/packager/README.md @@ -62,7 +62,7 @@ if the option is boolean `1/0` or `true/false` is accepted. Here are the current options the packager accepts: * `dev` boolean, defaults to true: sets a global `__DEV__` variable - which will effect how the React Nativeg core libraries behave. + which will effect how the React Native core libraries behave. * `minify` boolean, defaults to false: whether to minify the bundle. * `runModule` boolean, defaults to true: whether to require your entry point module. So if you requested `moduleName`, this option will add From 435125f4a08de906429d8ecc66cb963b5ee64c44 Mon Sep 17 00:00:00 2001 From: Andy Street Date: Wed, 3 Jun 2015 17:54:40 -0100 Subject: [PATCH 05/25] Revert "[ReactNative] Run UIExplorer tests on sandcastle" --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ---- Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ---- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index 9d3adb2ee..d72262e78 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,10 +50,6 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#if RUNNING_ON_CI - jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; -#endif - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 349bd9a28..245054621 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.internal.uiexplorer.local + com.facebook.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,8 +22,6 @@ 1 LSRequiresIPhoneOS - NSLocationWhenInUseUsageDescription - You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -36,6 +34,8 @@ 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/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index 7ff8c6379..aa524ef56 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { - cacheDownloadBlock(cached, data, error); + for (RCTCachedDataDownloadBlock block in blocks) { + block(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 8a7e739bb..9c0cacf70 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,11 +35,7 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; -#if RUNNING_ON_CI - _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; -#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; -#endif } return self; } From 5a191dadfc61b1ad1dc3b90560fd2ab1fb997370 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 11:39:39 -0700 Subject: [PATCH 06/25] [react-packager] Add support for nested node_modules Summary: @public The packager's resolver started out imitating node-haste, which meant that we didn't support nested modules. Now this is a problem. Bigger projects are bound to have versions of different versions of the same package at different levels of the dependency tree. This makes loading dependencies lazy for node_modules and implements the node resolution algorithm. However, it also mantains that some modules are still "haste" format, which currently defaults to "react-native" and "react-tools". Finally, this means ~5 seconds speed up on every server start. This should also have a big impact on open source users with projects with big node_modules. Test Plan: 1- test the app with --reset-cache 2- click around test and production apps 3- update the OSS library 4- create a new project 5- npm install multiple modules 6- create some version conflict in your project 7- make sure we do the "right" thing 8- test file changes to make sure it works --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 + .../__tests__/DependencyGraph-test.js | 1959 +++++++++++++++-- .../haste/DependencyGraph/index.js | 382 +++- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 2125 insertions(+), 377 deletions(-) create mode 100644 packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 90db1c4ad..3c1a1d26d 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,6 +8,9 @@ */ 'use strict'; +var Promise = require('bluebird'); +var isAbsolutePath = require('absolute-path'); + function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -17,17 +20,13 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } + if (!isAbsolutePath(fields.path)) { + throw new Error('Expected absolute path but found: ' + fields.path); + } this.path = fields.path; - if (!fields.dependencies) { - throw new Error('Missing required fields dependencies'); - } this.dependencies = fields.dependencies; - this.resolveDependency = fields.resolveDependency; - - this.entry = fields.entry || false; - this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -50,12 +49,30 @@ function ModuleDescriptor(fields) { this._fields = fields; } +ModuleDescriptor.prototype.loadDependencies = function(loader) { + if (!this.dependencies) { + if (this._loadingDependencies) { + return this._loadingDependencies; + } + + var self = this; + this._loadingDependencies = loader(this).then(function(dependencies) { + self.dependencies = dependencies; + }); + return this._loadingDependencies; + } + + return Promise.resolve(this.dependencies); +}; + ModuleDescriptor.prototype.toJSON = function() { - return { - id: this.id, - path: this.path, - dependencies: this.dependencies - }; + var ret = {}; + Object.keys(this).forEach(function(prop) { + if (prop[0] !== '_' && typeof this[prop] !== 'function') { + ret[prop] = this[prop]; + } + }, this); + return ret; }; module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js new file mode 100644 index 000000000..99bed5df7 --- /dev/null +++ b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest + .dontMock('absolute-path') + .dontMock('../ModuleDescriptor'); + + +describe('ModuleDescriptor', function() { + var ModuleDescriptor; + var Promise; + + beforeEach(function() { + ModuleDescriptor = require('../ModuleDescriptor'); + Promise = require('bluebird'); + }); + + describe('constructor', function() { + it('should validate fields', function() { + /* eslint no-new:0*/ + expect(function() { + new ModuleDescriptor({}); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: 'foo', + }); + }).toThrow(); + + expect(function() { + new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + }); + }).toThrow(); + + var m = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + expect(m.toJSON()).toEqual({ + altId:undefined, + dependencies: undefined, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + }); + }); + + describe('loadDependencies', function() { + pit('should load dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + + pit('should load cached dependencies', function() { + var mod = new ModuleDescriptor({ + id: 'foo', + path: '/foo', + isAsset: true, + resolution: 1, + }); + + return mod.loadDependencies(function() { + return Promise.resolve([1, 2]); + }).then(function() { + return mod.loadDependencies(function() { + throw new Error('no!'); + }); + }).then(function() { + expect(mod.dependencies).toEqual([1, 2]); + }); + }); + }); +}); 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 c247e59d3..6c0fd05f6 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 @@ -10,11 +10,12 @@ jest .dontMock('../index') + .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .setMock('../../../ModuleDescriptor', function(data) {return data;}); + .dontMock('../../../ModuleDescriptor'); jest.mock('fs'); @@ -34,6 +35,14 @@ describe('DependencyGraph', function() { }; }); + // There are a lot of crap in ModuleDescriptors, this maps an array + // to get the relevant data. + function getDataFromModules(modules) { + return modules.map(function(module) { + return module.toJSON(); + }); + } + describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -58,11 +67,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -95,11 +126,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined + }, ]); }); }); @@ -126,20 +179,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'] + dependencies: ['./a.json'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -167,15 +231,31 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, - { id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -205,20 +285,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'] + dependencies: ['./imgs/a.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -253,8 +344,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', @@ -264,7 +355,13 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ] + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -272,20 +369,32 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -317,14 +426,20 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'] + dependencies: ['./imgs/a.png', 'image!a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'rootPackage/imgs/a.png', @@ -332,6 +447,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, { id: 'image!a', @@ -339,6 +458,10 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -368,11 +491,33 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, - {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['a'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'a', + altId: '/root/a.js', + path: '/root/a.js', + dependencies: ['index'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -402,13 +547,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/main', + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -433,13 +596,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -468,14 +648,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'EpicModule', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -503,13 +700,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, - { id: 'aPackage/lib/index', + { + id: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -534,13 +748,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, - { id: 'test/lib/index', + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -568,10 +799,21 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -612,18 +854,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) + return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', + { + id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'] + dependencies: ['c'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'c', + { + id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -659,17 +915,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage', + { + id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -693,12 +963,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'] + dependencies: ['lolomg'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); }); @@ -732,16 +1009,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -776,16 +1066,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'] + dependencies: ['aPackage/subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -820,24 +1124,56 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', + altId: undefined, path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'] + dependencies: ['./subdir/lolynot'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/subdir/lolynot', + { + id: 'aPackage/subdir/lolynot', + altId: undefined, path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'] + dependencies: ['../other'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/other', + { + id: 'aPackage/other', + altId: undefined, path: '/root/aPackage/other.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -870,16 +1206,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', + altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/client', + { + id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -912,16 +1264,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', + altId: undefined, path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -956,16 +1322,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1000,16 +1379,29 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1054,28 +1446,58 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'] + dependencies: ['./node', './dir/server.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'] + dependencies: ['./not-browser'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1120,20 +1542,576 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'] + dependencies: ['node-package'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + describe('node_modules', function() { + pit('should work with nested node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with specific paths', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/lol', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/lol.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('nested node_modules with browser field', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar/lol");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + browser: { + './lol': './wow' + } + }), + 'main.js': 'bar 1 module', + 'lol.js': '', + 'wow.js': '', + }, + } + }, + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + browser: './main2', + }), + 'main2.js': 'bar 2 module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo', 'bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: ['bar/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/wow', + path: '/root/node_modules/foo/node_modules/bar/wow.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main2', + path: '/root/node_modules/bar/main2.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('node_modules should support multi level', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("bar");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': '', + }, + }, + 'path': { + 'to': { + 'bar.js': [ + '/**', + ' * @providesModule bar', + ' */', + 'require("foo")', + ].join('\n'), + }, + 'node_modules': {}, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + path: '/root/path/to/bar.js', + altId: '/root/path/to/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should selectively ignore providesModule in node_modules', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("shouldWork");', + 'require("dontWork");', + 'require("wontWork");', + ].join('\n'), + 'node_modules': { + 'react-tools': { + 'package.json': JSON.stringify({ + name: 'react-tools', + main: 'main.js', + }), + 'main.js': [ + '/**', + ' * @providesModule shouldWork', + ' */', + 'require("submodule");', + ].join('\n'), + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule dontWork', + ' */', + 'hi();', + ].join('\n'), + }, + 'submodule': { + 'package.json': JSON.stringify({ + name: 'submodule', + main: 'main.js', + }), + 'main.js': 'log()', + }, + } + }, + 'ember': { + 'package.json': JSON.stringify({ + name: 'ember', + main: 'main.js', + }), + 'main.js':[ + '/**', + ' * @providesModule wontWork', + ' */', + 'hi();', + ].join('\n'), + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['shouldWork', 'dontWork', 'wontWork'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'shouldWork', + path: '/root/node_modules/react-tools/main.js', + altId:'react-tools/main', + dependencies: ['submodule'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'submodule/main', + path: '/root/node_modules/react-tools/node_modules/submodule/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + + pit('should ignore modules it cant find (assumes own require system)', function() { + // For example SourceMap.js implements it's own require system. + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo/lol");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo/lol'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1187,22 +2165,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1239,22 +2231,36 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [] - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1291,19 +2297,31 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1342,7 +2360,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1354,27 +2372,54 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] - }, - { id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'] - }, - { id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'] - }, - { id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'] - }, + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -1400,32 +2445,50 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'] + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'] - }, - { id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - }, - ]); + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + isAsset: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, + }, + ]); }); }); }); @@ -1452,30 +2515,49 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'] + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) .toEqual([ - { id: 'index', altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'] - }, - { id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, + { + id: 'index', + altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolveDependency: undefined, }, ]); }); @@ -1520,7 +2602,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -1532,21 +2614,42 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'] + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); @@ -1584,25 +2687,407 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.load().then(function() { + return dgraph.getOrderedDependencies('/root/index.js').then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.load().then(function() { - expect(dgraph.getOrderedDependencies('/root/index.js')) + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) .toEqual([ - { id: 'index', altId: '/root/index.js', + { + id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'] + dependencies: ['aPackage', 'foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'aPackage/main', + { + id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [] + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, - { id: 'foo', + { + id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'] + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('updates package.json', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); + triggerFileChange('change', 'index.js', root); + + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['bPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bPackage/main', + path: '/root/aPackage/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('changes to browser field', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'aPackage', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'aPackage/browser', + path: '/root/aPackage/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('removes old package from cache', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("aPackage")', + ].join('\n'), + 'aPackage': { + 'package.json': JSON.stringify({ + name: 'aPackage', + main: 'main.js' + }), + 'main.js': 'main', + 'browser.js': 'browser', + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function() { + filesystem.root.aPackage['package.json'] = JSON.stringify({ + name: 'bPackage', + main: 'main.js', + }); + triggerFileChange('change', 'package.json', '/root/aPackage'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'require("bar");\nfoo module', + 'node_modules': { + 'bar': { + 'package.json': JSON.stringify({ + name: 'bar', + main: 'main.js', + }), + 'main.js': 'bar 1 module', + }, + } + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: ['bar'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'bar/main', + altId: undefined, + path: '/root/node_modules/foo/node_modules/bar/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + + filesystem.root.node_modules.foo['main.js'] = 'lol'; + triggerFileChange('change', 'main.js', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/main', + altId: undefined, + path: '/root/node_modules/foo/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + }); + + pit('should update node package main changes', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("foo");', + ].join('\n'), + 'node_modules': { + 'foo': { + 'package.json': JSON.stringify({ + name: 'foo', + main: 'main.js', + }), + 'main.js': 'foo module', + 'browser.js': 'foo module', + }, + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ + name: 'foo', + main: 'main.js', + browser: 'browser.js', + }); + triggerFileChange('change', 'package.json', '/root/node_modules/foo'); + + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { + expect(getDataFromModules(deps2)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['foo'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'foo/browser', + altId: undefined, + path: '/root/node_modules/foo/browser.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 0881e5dc7..fc899cb2f 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,3 +1,6 @@ +// TODO +// Fix it to work with tests + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -19,6 +22,7 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); +var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -45,7 +49,11 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - } + }, + _providesModuleNodeModules: { + type: 'array', + default: ['react-tools', 'react-native'], + }, }); function DependecyGraph(options) { @@ -69,6 +77,8 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); + this._providesModuleNodeModules = opts._providesModuleNodeModules; + // Kick off the search process to precompute the dependency graph. this._init(); } @@ -90,59 +100,101 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } + return this.load().then(function() { + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); + } - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } - var self = this; - var deps = []; - var visited = Object.create(null); + var self = this; + var deps = []; + var visited = Object.create(null); - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; - // Recursively collect the dependency list. - function collect(module) { - deps.push(module); + // Recursively collect the dependency list. + function collect(mod) { + deps.push(mod); - module.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(module, id); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - module.id - ); - return; + if (mod.dependencies == null) { + return mod.loadDependencies(function() { + return readFile(mod.path, 'utf8').then(function(content) { + return extractRequires(content); + }); + }).then(function() { + return iter(mod); + }); } - if (!visited[dep.id]) { - visited[dep.id] = true; - collect(dep); - } + return iter(mod); + } + + function iter(mod) { + var p = Promise.resolve(); + mod.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(mod, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + mod.id + ); + return; + } + + p = p.then(function() { + if (!visited[realId(dep)]) { + visited[realId(dep)] = true; + return collect(dep); + } + return null; + }); + }); + return p; + } + + visited[realId(module)] = true; + return collect(module).then(function() { + return deps; }); - } - - visited[module.id] = true; - collect(module); - - return deps; + }.bind(this)); }; +function browserFieldRedirect(packageJson, modulePath, isMain) { + if (packageJson.browser && typeof packageJson.browser === 'object') { + if (isMain) { + var tmpMain = packageJson.browser[modulePath] || + packageJson.browser[sansExtJs(modulePath)] || + packageJson.browser[withExtJs(modulePath)]; + if (tmpMain) { + return tmpMain; + } + } else { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + return path.join(packageJson._root, tmpModulePath); + } + } + } + return modulePath; +} + /** * Given a module descriptor `fromModule` return the module descriptor for * the required module `depModuleId`. It could be top-level or relative, @@ -157,7 +209,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARINING: Cannot find asset:', assetMatch[1]); + debug('WARNING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -177,19 +229,39 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } + + var packageName = depModuleId.replace(/\/.+/, ''); + packageJson = this._lookupNodePackage(fromModule.path, packageName); + + if (packageJson != null && packageName !== depModuleId) { + modulePath = path.join( + packageJson._root, + path.relative(packageName, depModuleId) + ); + + modulePath = browserFieldRedirect(packageJson, modulePath); + + dep = this._graph[withExtJs(modulePath)]; + if (dep != null) { + return dep; + } + } + // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (this._moduleById[sansExtJs(depModuleId)]) { + if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - - // We are being forgiving here and raising an error because we could be - // processing a file that uses it's own require system. if (packageJson == null) { + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; + } + + // We are being forgiving here and not raising an error because we could be + // processing a file that uses it's own require system. + if (packageJson == null || packageName !== depModuleId) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -198,33 +270,8 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - var main; - - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - if (packageJson.browser && typeof packageJson.browser === 'object') { - var tmpMain = packageJson.browser[main] || - packageJson.browser[withExtJs(main)] || - packageJson.browser[sansExtJs(main)]; - if (tmpMain) { - main = tmpMain; - } - } - - modulePath = withExtJs(path.join(packageJson._root, main)); - dep = this._graph[modulePath]; - - // Some packages use just a dir and rely on an index.js inside that dir. - if (dep == null) { - dep = this._graph[path.join(packageJson._root, main, 'index.js')]; - } - + // We are requiring node or a haste package via it's main file. + dep = this._resolvePackageMain(packageJson); if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -248,15 +295,7 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - - if (packageJson.browser && typeof packageJson.browser === 'object') { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - modulePath = path.join(packageJson._root, tmpModulePath); - } - } + modulePath = browserFieldRedirect(packageJson, modulePath); // JS modules can be required without extensios. if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { @@ -291,6 +330,25 @@ DependecyGraph.prototype.resolveDependency = function( } }; +DependecyGraph.prototype._resolvePackageMain = function(packageJson) { + var main; + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + main = browserFieldRedirect(packageJson, main, true); + + var modulePath = withExtJs(path.join(packageJson._root, main)); + + return this._graph[modulePath] || + // Some packages use just a dir and rely on an index.js inside that dir. + this._graph[path.join(packageJson._root, main, 'index.js')]; +}; + /** * Intiates the filewatcher and kicks off the search process. */ @@ -339,9 +397,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -358,10 +416,8 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { - var self = this; - var packagePath; - for (var i = 0; i < files.length ; i++) { + for (var i = 0; i < files.length; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -406,12 +462,16 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - this._packagesById[packageJson.name] = packageJson; + if (!this._isInNodeModules(packageJson._root)) { + this._packagesById[packageJson.name] = packageJson; + } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - delete this._packagesById[packageJson.name]; + if (!this._isInNodeModules(packageJson._root)) { + delete this._packagesById[packageJson.name]; + } }; /** @@ -441,6 +501,14 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } + if (this._isInNodeModules(modulePath)) { + moduleData.id = this._lookupName(modulePath); + moduleData.dependencies = null; + module = new ModuleDescriptor(moduleData); + this._updateGraphWithModule(module); + return Promise.resolve(module); + } + var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -484,6 +552,10 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; + if (this._isInNodeModules(module.path)) { + return; + } + // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -504,6 +576,10 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; + if (this._isInNodeModules(module.path)) { + return; + } + if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -527,28 +603,14 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - var packageByRoot = this._packageByRoot; + return lookupPackage(path.dirname(modulePath), this._packageByRoot); +}; - /** - * Auxiliary function to recursively lookup a package. - */ - function lookupPackage(currDir) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir)); - } - } - } - - return lookupPackage(path.dirname(modulePath)); +/** + * Find the nearest node package to a module. + */ +DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { + return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); }; /** @@ -573,12 +635,14 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; + var packageJson; + if (isPackage) { + packageJson = this._packageByRoot[path.dirname(absPath)]; + } + if (eventType === 'delete') { - if (isPackage) { - var packageJson = this._packageByRoot[path.dirname(absPath)]; - if (packageJson) { - this._removePackageFromIndices(packageJson); - } + if (isPackage && packageJson) { + this._removePackageFromIndices(packageJson); } else { var module = this._graph[absPath]; if (module == null) { @@ -591,7 +655,14 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - return self._processPackage(absPath); + self._removePackageFromIndices(packageJson); + return self._processPackage(absPath) + .then(function(p) { + return self._resolvePackageMain(p); + }) + .then(function(mainModule) { + return self._processModule(mainModule.path); + }); } return self._processModule(absPath); }); @@ -675,6 +746,25 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; +DependecyGraph.prototype._isInNodeModules = function(file) { + var inNodeModules = file.indexOf('/node_modules/') !== -1; + + if (!inNodeModules) { + return false; + } + + var dirs = this._providesModuleNodeModules; + + for (var i = 0; i < dirs.length; i++) { + var index = file.indexOf(dirs[i]); + if (index !== -1) { + return file.slice(index).indexOf('/node_modules/') !== -1; + } + } + + return true; +}; + /** * Extract all required modules from a `code` string. */ @@ -784,6 +874,54 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } +function realId(module) { + if (module._realId) { + return module._realId; + } + + var hash = crypto.createHash('md5'); + hash.update(module.id); + hash.update(module.path); + Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); + return module._realId; +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupPackage(currDir, packageByRoot) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir), packageByRoot); + } + } +} + +/** + * Auxiliary function to recursively lookup a package. + */ +function lookupNodePackage(currDir, packageByRoot, packageName) { + if (currDir === '/') { + return null; + } + var packageRoot = path.join(currDir, 'node_modules', packageName); + + var packageJson = packageByRoot[packageRoot]; + if (packageJson) { + return packageJson; + } else { + return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); + } +} + function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 9bc8b8b95..1f8c95479 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return deps; + return Promise.resolve(deps); }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index da68785ea..d7a8c0eb1 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -91,9 +91,8 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.load() - .then(function() { - var dependencies = depGraph.getOrderedDependencies(main); + return depGraph.getOrderedDependencies(main) + .then(function(dependencies) { var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 7be471d1fee2646e39046682acda6a542add1154 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 12:53:08 -0700 Subject: [PATCH 07/25] visualize padding and margin in inspector Summary: This shows margin and padding visually when inspecting an element. @public Test Plan: Go to the "UIExplorer", to the page. Open the inspector, and start selecting things. Padding and margin should be indicated. (Padding in dark blue and margin in orange). --- .../ReactIOS/InspectorOverlay/BorderBox.js | 38 ++++++++++ .../ReactIOS/InspectorOverlay/ElementBox.js | 74 +++++++++++++++++++ .../InspectorOverlay.js | 11 ++- Libraries/ReactIOS/resolveBoxStyle.js | 61 +++++++++++++++ 4 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 Libraries/ReactIOS/InspectorOverlay/BorderBox.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/ElementBox.js rename Libraries/ReactIOS/{ => InspectorOverlay}/InspectorOverlay.js (89%) create mode 100644 Libraries/ReactIOS/resolveBoxStyle.js diff --git a/Libraries/ReactIOS/InspectorOverlay/BorderBox.js b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js new file mode 100644 index 000000000..caee8ccd6 --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule BorderBox + * @flow + */ +'use strict'; + +var React = require('React'); +var View = require('View'); + +class BorderBox extends React.Component { + render() { + var box = this.props.box; + if (!box) { + return this.props.children; + } + var style = { + borderTopWidth: box.top, + borderBottomWidth: box.bottom, + borderLeftWidth: box.left, + borderRightWidth: box.right, + }; + return ( + + {this.props.children} + + ); + } +} + +module.exports = BorderBox; + diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js new file mode 100644 index 000000000..a94312b9c --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ElementBox + * @flow + */ +'use strict'; + +var React = require('React'); +var View = require('View'); +var StyleSheet = require('StyleSheet'); +var BorderBox = require('BorderBox'); +var resolveBoxStyle = require('resolveBoxStyle'); + +var flattenStyle = require('flattenStyle'); + +class ElementBox extends React.Component { + render() { + var style = flattenStyle(this.props.style) || {}; + var margin = resolveBoxStyle('margin', style); + var padding = resolveBoxStyle('padding', style); + var frameStyle = this.props.frame; + if (margin) { + frameStyle = { + top: frameStyle.top - margin.top, + left: frameStyle.left - margin.left, + height: frameStyle.height + margin.top + margin.bottom, + width: frameStyle.width + margin.left + margin.right, + }; + } + var contentStyle = { + width: this.props.frame.width, + height: this.props.frame.height, + }; + if (padding) { + contentStyle = { + width: contentStyle.width - padding.left - padding.right, + height: contentStyle.height - padding.top - padding.bottom, + }; + } + return ( + + + + + + + + ); + } +} + +var styles = StyleSheet.create({ + frame: { + position: 'absolute', + }, + content: { + backgroundColor: 'rgba(0, 0, 255, 0.1)', + }, + padding: { + borderColor: 'rgba(182, 217, 167, 0.3)', + }, + margin: { + borderColor: 'rgba(248, 194, 136, 0.3)', + }, +}); + +module.exports = ElementBox; + diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js similarity index 89% rename from Libraries/ReactIOS/InspectorOverlay.js rename to Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index eeb6e7965..97d08b90b 100644 --- a/Libraries/ReactIOS/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -17,6 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var UIManager = require('NativeModules').UIManager; var View = require('View'); +var ElementBox = require('ElementBox'); var InspectorOverlay = React.createClass({ getInitialState: function() { @@ -34,9 +35,11 @@ var InspectorOverlay = React.createClass({ (nativeViewTag, left, top, width, height) => { var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag); var hierarchy = Inspector.getOwnerHierarchy(instance); + var publicInstance = instance.getPublicInstance(); this.setState({ hierarchy, - frame: {left, top, width, height} + frame: {left, top, width, height}, + style: publicInstance.props ? publicInstance.props.style : {}, }); } ); @@ -59,7 +62,7 @@ var InspectorOverlay = React.createClass({ ? 'flex-start' : 'flex-end'; - content.push(); + content.push(); content.push(); } return ( @@ -97,10 +100,6 @@ var styles = StyleSheet.create({ right: 0, bottom: 0, }, - frame: { - position: 'absolute', - backgroundColor: 'rgba(155,155,255,0.3)', - }, info: { backgroundColor: 'rgba(0, 0, 0, 0.7)', padding: 10, diff --git a/Libraries/ReactIOS/resolveBoxStyle.js b/Libraries/ReactIOS/resolveBoxStyle.js new file mode 100644 index 000000000..15342b56a --- /dev/null +++ b/Libraries/ReactIOS/resolveBoxStyle.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule resolveBoxStyle + * @flow + */ +'use strict'; + +/** + * Resolve a style property into it's component parts, e.g. + * + * resolveProperties('margin', {margin: 5, marginBottom: 10}) + * -> + * {top: 5, left: 5, right: 5, bottom: 10} + * + * If none are set, returns false. + */ +function resolveBoxStyle(prefix: String, style: Object): ?Object { + var res = {}; + var subs = ['top', 'left', 'bottom', 'right']; + var set = false; + if (style[prefix]) { + subs.forEach(sub => { + res[sub] = style[prefix]; + }); + set = true; + } + if (style[prefix + 'Vertical']) { + res.top = res.bottom = style[prefix + 'Vertical']; + set = true; + } + if (style[prefix + 'Horizontal']) { + res.left = res.right = style[prefix + 'Horizontal']; + set = true; + } + subs.forEach(sub => { + var val = style[prefix + capFirst(sub)]; + if (val) { + res[sub] = val; + } + if (res[sub]) { + set = true; + } + }); + if (!set) { + return; + } + return res; +} + +function capFirst(text) { + return text[0].toUpperCase() + text.slice(1); +} + +module.exports = resolveBoxStyle; + From 074fa759a66f862c62b3fd55ce0ff20b749aa57e Mon Sep 17 00:00:00 2001 From: jmstout Date: Wed, 3 Jun 2015 12:56:32 -0700 Subject: [PATCH 08/25] [Touchable] Add custom delay props to Touchable components Summary: @public This PR adds quite a bit of functionality to the Touchable components, allowing the ms delays of each of the handlers (`onPressIn, onPressOut, onPress, onLongPress`) to be configured. It adds the following props to `TouchableWithoutFeedback, TouchableOpacity, and TouchableHighlight`: ```javascript /** * Delay in ms, from the release of the touch, before onPress is called. */ delayOnPress: React.PropTypes.number, /** * Delay in ms, from the start of the touch, before onPressIn is called. */ delayOnPressIn: React.PropTypes.number, /** * Delay in ms, from the release of the touch, before onPressOut is called. */ delayOnPressOut: React.PropTypes.number, /** * Delay in ms, from onPressIn, before onLongPress is called. */ delayOnLongPress: React.PropTypes.number, ``` `TouchableHighlight` also gets an additional set of props: ```javascript /** * Delay in ms, from the start of the touch, before the highlight is shown. */ delayHighlightShow: React.PropTypes.number, /** * Del ... ``` Closes https://github.com/facebook/react-native/pull/1255 Github Author: jmstout Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/TouchableExample.js | 46 ++++++++++++++++ .../Touchable/TouchableHighlight.js | 18 ++++++- .../Components/Touchable/TouchableOpacity.js | 53 ++++++++++++++++--- .../Touchable/TouchableWithoutFeedback.js | 35 +++++++++++- .../Touchable/ensurePositiveDelayProps.js | 24 +++++++++ .../interactions/Touchable/Touchable.js | 28 +++++++--- 6 files changed, 187 insertions(+), 17 deletions(-) create mode 100644 Libraries/Components/Touchable/ensurePositiveDelayProps.js diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 45a679b31..acbba3629 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -75,6 +75,14 @@ exports.examples = [ render: function(): ReactElement { return ; }, +}, { + title: 'Touchable delay for events', + description: ' components also accept delayPressIn, ' + + 'delayPressOut, and delayLongPress as props. These props impact the ' + + 'timing of feedback events.', + render: function(): ReactElement { + return ; + }, }]; var TextOnPressBox = React.createClass({ @@ -148,6 +156,44 @@ var TouchableFeedbackEvents = React.createClass({ }, }); +var TouchableDelayEvents = React.createClass({ + getInitialState: function() { + return { + eventLog: [], + }; + }, + render: function() { + return ( + + + this._appendEvent('press')} + delayPressIn={400} + onPressIn={() => this._appendEvent('pressIn - 400ms delay')} + delayPressOut={1000} + onPressOut={() => this._appendEvent('pressOut - 1000ms delay')} + delayLongPress={800} + onLongPress={() => this._appendEvent('longPress - 800ms delay')}> + + Press Me + + + + + {this.state.eventLog.map((e, ii) => {e})} + + + ); + }, + _appendEvent: function(eventName) { + var limit = 6; + var eventLog = this.state.eventLog.slice(0, limit - 1); + eventLog.unshift(eventName); + this.setState({eventLog}); + }, +}); + var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'}; var styles = StyleSheet.create({ diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 533652f65..dcbfbeee1 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -23,6 +23,7 @@ var View = require('View'); var cloneWithProps = require('cloneWithProps'); var ensureComponentIsNative = require('ensureComponentIsNative'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var keyOf = require('keyOf'); var merge = require('merge'); var onlyChild = require('onlyChild'); @@ -111,6 +112,7 @@ var TouchableHighlight = React.createClass({ }, componentDidMount: function() { + ensurePositiveDelayProps(this.props); ensureComponentIsNative(this.refs[CHILD_REF]); }, @@ -119,6 +121,7 @@ var TouchableHighlight = React.createClass({ }, componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); if (nextProps.activeOpacity !== this.props.activeOpacity || nextProps.underlayColor !== this.props.underlayColor || nextProps.style !== this.props.style) { @@ -152,7 +155,8 @@ var TouchableHighlight = React.createClass({ touchableHandlePress: function() { this.clearTimeout(this._hideTimeout); this._showUnderlay(); - this._hideTimeout = this.setTimeout(this._hideUnderlay, 100); + this._hideTimeout = this.setTimeout(this._hideUnderlay, + this.props.delayPressOut || 100); this.props.onPress && this.props.onPress(); }, @@ -164,6 +168,18 @@ var TouchableHighlight = React.createClass({ return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant! }, + touchableGetHighlightDelayMS: function() { + return this.props.delayPressIn; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + _showUnderlay: function() { this.refs[UNDERLAY_REF].setNativeProps(this.state.activeUnderlayProps); this.refs[CHILD_REF].setNativeProps(this.state.activeProps); diff --git a/Libraries/Components/Touchable/TouchableOpacity.js b/Libraries/Components/Touchable/TouchableOpacity.js index 8d93cd841..1549faea2 100644 --- a/Libraries/Components/Touchable/TouchableOpacity.js +++ b/Libraries/Components/Touchable/TouchableOpacity.js @@ -15,11 +15,13 @@ var NativeMethodsMixin = require('NativeMethodsMixin'); var POPAnimationMixin = require('POPAnimationMixin'); var React = require('React'); +var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var cloneWithProps = require('cloneWithProps'); var ensureComponentIsNative = require('ensureComponentIsNative'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var flattenStyle = require('flattenStyle'); var keyOf = require('keyOf'); var onlyChild = require('onlyChild'); @@ -47,7 +49,7 @@ var onlyChild = require('onlyChild'); */ var TouchableOpacity = React.createClass({ - mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin], + mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin], propTypes: { ...TouchableWithoutFeedback.propTypes, @@ -69,6 +71,7 @@ var TouchableOpacity = React.createClass({ }, componentDidMount: function() { + ensurePositiveDelayProps(this.props); ensureComponentIsNative(this.refs[CHILD_REF]); }, @@ -76,6 +79,10 @@ var TouchableOpacity = React.createClass({ ensureComponentIsNative(this.refs[CHILD_REF]); }, + componentWillReceiveProps: function(nextProps) { + ensurePositiveDelayProps(nextProps); + }, + setOpacityTo: function(value) { if (POPAnimationMixin) { // Reset with animation if POP is available @@ -83,6 +90,7 @@ var TouchableOpacity = React.createClass({ var anim = { type: this.AnimationTypes.linear, property: this.AnimationProperties.opacity, + duration: 0.15, toValue: value, }; this.startAnimation(CHILD_REF, anim); @@ -99,20 +107,26 @@ var TouchableOpacity = React.createClass({ * defined on your component. */ touchableHandleActivePressIn: function() { - this.refs[CHILD_REF].setNativeProps({ - opacity: this.props.activeOpacity - }); + this.clearTimeout(this._hideTimeout); + this._hideTimeout = null; + this._opacityActive(); this.props.onPressIn && this.props.onPressIn(); }, touchableHandleActivePressOut: function() { - var child = onlyChild(this.props.children); - var childStyle = flattenStyle(child.props.style) || {}; - this.setOpacityTo(childStyle.opacity === undefined ? 1 : childStyle.opacity); + if (!this._hideTimeout) { + this._opacityInactive(); + } this.props.onPressOut && this.props.onPressOut(); }, touchableHandlePress: function() { + this.clearTimeout(this._hideTimeout); + this._opacityActive(); + this._hideTimeout = this.setTimeout( + this._opacityInactive, + this.props.delayPressOut || 100 + ); this.props.onPress && this.props.onPress(); }, @@ -125,7 +139,30 @@ var TouchableOpacity = React.createClass({ }, touchableGetHighlightDelayMS: function() { - return 0; + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function() { + return this.props.delayLongPress === 0 ? 0 : + this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function() { + return this.props.delayPressOut; + }, + + _opacityActive: function() { + this.setOpacityTo(this.props.activeOpacity); + }, + + _opacityInactive: function() { + this.clearTimeout(this._hideTimeout); + this._hideTimeout = null; + var child = onlyChild(this.props.children); + var childStyle = flattenStyle(child.props.style) || {}; + this.setOpacityTo( + childStyle.opacity === undefined ? 1 : childStyle.opacity + ); }, render: function() { diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index cd9ea02fd..9449f79bb 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -12,7 +12,9 @@ 'use strict'; var React = require('React'); +var TimerMixin = require('react-timer-mixin'); var Touchable = require('Touchable'); +var ensurePositiveDelayProps = require('ensurePositiveDelayProps'); var onlyChild = require('onlyChild'); /** @@ -31,7 +33,7 @@ type Event = Object; * one of the primary reason a "web" app doesn't feel "native". */ var TouchableWithoutFeedback = React.createClass({ - mixins: [Touchable.Mixin], + mixins: [TimerMixin, Touchable.Mixin], propTypes: { /** @@ -42,12 +44,32 @@ var TouchableWithoutFeedback = React.createClass({ onPressIn: React.PropTypes.func, onPressOut: React.PropTypes.func, onLongPress: React.PropTypes.func, + /** + * Delay in ms, from the start of the touch, before onPressIn is called. + */ + delayPressIn: React.PropTypes.number, + /** + * Delay in ms, from the release of the touch, before onPressOut is called. + */ + delayPressOut: React.PropTypes.number, + /** + * Delay in ms, from onPressIn, before onLongPress is called. + */ + delayLongPress: React.PropTypes.number, }, getInitialState: function() { return this.touchableGetInitialState(); }, + componentDidMount: function() { + ensurePositiveDelayProps(this.props); + }, + + componentWillReceiveProps: function(nextProps: Object) { + ensurePositiveDelayProps(nextProps); + }, + /** * `Touchable.Mixin` self callbacks. The mixin will invoke these if they are * defined on your component. @@ -73,7 +95,16 @@ var TouchableWithoutFeedback = React.createClass({ }, touchableGetHighlightDelayMS: function(): number { - return 0; + return this.props.delayPressIn || 0; + }, + + touchableGetLongPressDelayMS: function(): number { + return this.props.delayLongPress === 0 ? 0 : + this.props.delayLongPress || 500; + }, + + touchableGetPressOutDelayMS: function(): number { + return this.props.delayPressOut || 0; }, render: function(): ReactElement { diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js new file mode 100644 index 000000000..4c6525a54 --- /dev/null +++ b/Libraries/Components/Touchable/ensurePositiveDelayProps.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ensurePositiveDelayProps + * @flow + */ +'use strict'; + +var invariant = require('invariant'); + +var ensurePositiveDelayProps = function(props: any) { + invariant( + !(props.delayPressIn < 0 || props.delayPressOut < 0 || + props.delayLongPress < 0), + 'Touchable components cannot have negative delay properties' + ); +}; + +module.exports = ensurePositiveDelayProps; diff --git a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js index 37c423827..2a0dd1813 100644 --- a/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js +++ b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js @@ -232,6 +232,8 @@ var PRESS_EXPAND_PX = 20; var LONG_PRESS_THRESHOLD = 500; +var LONG_PRESS_DELAY_MS = LONG_PRESS_THRESHOLD - HIGHLIGHT_DELAY_MS; + var LONG_PRESS_ALLOWED_MOVEMENT = 10; // Default amount "active" region protrudes beyond box @@ -276,7 +278,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * + * | RESPONDER_GRANT (HitRect) * v - * +---------------------------+ DELAY +-------------------------+ T - DELAY +------------------------------+ + * +---------------------------+ DELAY +-------------------------+ T + DELAY +------------------------------+ * |RESPONDER_INACTIVE_PRESS_IN|+-------->|RESPONDER_ACTIVE_PRESS_IN| +------------> |RESPONDER_ACTIVE_LONG_PRESS_IN| * +---------------------------+ +-------------------------+ +------------------------------+ * + ^ + ^ + ^ @@ -288,7 +290,7 @@ var LONG_PRESS_ALLOWED_MOVEMENT = 10; * |RESPONDER_INACTIVE_PRESS_OUT|+------->|RESPONDER_ACTIVE_PRESS_OUT| |RESPONDER_ACTIVE_LONG_PRESS_OUT| * +----------------------------+ +--------------------------+ +-------------------------------+ * - * T - DELAY => LONG_PRESS_THRESHOLD - DELAY + * T + DELAY => LONG_PRESS_DELAY_MS + DELAY * * Not drawn are the side effects of each transition. The most important side * effect is the `touchableHandlePress` abstract method invocation that occurs @@ -348,12 +350,16 @@ var TouchableMixin = { // event to make sure it doesn't get reused in the event object pool. e.persist(); + this.pressOutDelayTimeout && clearTimeout(this.pressOutDelayTimeout); + this.pressOutDelayTimeout = null; + this.state.touchable.touchState = States.NOT_RESPONDER; this.state.touchable.responderID = dispatchID; this._receiveSignal(Signals.RESPONDER_GRANT, e); var delayMS = this.touchableGetHighlightDelayMS !== undefined ? - this.touchableGetHighlightDelayMS() : HIGHLIGHT_DELAY_MS; + Math.max(this.touchableGetHighlightDelayMS(), 0) : HIGHLIGHT_DELAY_MS; + delayMS = isNaN(delayMS) ? HIGHLIGHT_DELAY_MS : delayMS; if (delayMS !== 0) { this.touchableDelayTimeout = setTimeout( this._handleDelay.bind(this, e), @@ -363,9 +369,13 @@ var TouchableMixin = { this._handleDelay(e); } + var longDelayMS = + this.touchableGetLongPressDelayMS !== undefined ? + Math.max(this.touchableGetLongPressDelayMS(), 10) : LONG_PRESS_DELAY_MS; + longDelayMS = isNaN(longDelayMS) ? LONG_PRESS_DELAY_MS : longDelayMS; this.longPressDelayTimeout = setTimeout( this._handleLongDelay.bind(this, e), - LONG_PRESS_THRESHOLD - delayMS + longDelayMS + delayMS ); }, @@ -632,8 +642,14 @@ var TouchableMixin = { if (newIsHighlight && !curIsHighlight) { this._savePressInLocation(e); this.touchableHandleActivePressIn && this.touchableHandleActivePressIn(); - } else if (!newIsHighlight && curIsHighlight) { - this.touchableHandleActivePressOut && this.touchableHandleActivePressOut(); + } else if (!newIsHighlight && curIsHighlight && this.touchableHandleActivePressOut) { + if (this.touchableGetPressOutDelayMS && this.touchableGetPressOutDelayMS()) { + this.pressOutDelayTimeout = this.setTimeout(function() { + this.touchableHandleActivePressOut(); + }, this.touchableGetPressOutDelayMS()); + } else { + this.touchableHandleActivePressOut(); + } } if (IsPressingIn[curState] && signal === Signals.RESPONDER_RELEASE) { From b2b89c0b68ce5cdf06c5bd3b30014e3c1e50e3b9 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Wed, 3 Jun 2015 13:55:52 -0700 Subject: [PATCH 09/25] add style inspector + more margin + styles Summary: - make overlay transparent to avoid obscuring the app - show style in the inspector pane - show margin+padding values, also width/height and abs position @public Test Plan: Open the inspector somewhere, start selecting things. You should see correct padding, margin, and dimentions values; in addition to all style properties enumerated. --- Libraries/Inspector.js | 3 + .../ReactIOS/InspectorOverlay/BoxInspector.js | 113 ++++++++++++++++++ .../ReactIOS/InspectorOverlay/ElementBox.js | 6 +- .../InspectorOverlay/ElementProperties.js | 64 ++++++++++ .../InspectorOverlay/InspectorOverlay.js | 54 +++++---- .../InspectorOverlay/StyleInspector.js | 63 ++++++++++ .../{ => InspectorOverlay}/resolveBoxStyle.js | 8 +- 7 files changed, 277 insertions(+), 34 deletions(-) create mode 100644 Libraries/ReactIOS/InspectorOverlay/BoxInspector.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/ElementProperties.js create mode 100644 Libraries/ReactIOS/InspectorOverlay/StyleInspector.js rename Libraries/ReactIOS/{ => InspectorOverlay}/resolveBoxStyle.js (93%) diff --git a/Libraries/Inspector.js b/Libraries/Inspector.js index 5c70be2e8..e0017c3cf 100644 --- a/Libraries/Inspector.js +++ b/Libraries/Inspector.js @@ -50,6 +50,9 @@ function findInstanceByNativeTag(rootTag, nativeTag) { var containerID = ReactNativeTagHandles.tagToRootNodeID[rootTag]; var rootInstance = ReactNativeMount._instancesByContainerID[containerID]; var targetID = ReactNativeTagHandles.tagToRootNodeID[nativeTag]; + if (!targetID) { + return undefined; + } return findInstance(rootInstance, targetID); } diff --git a/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js new file mode 100644 index 000000000..e50d9869a --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule BoxInspector + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); +var resolveBoxStyle = require('resolveBoxStyle'); + +var blank = { + top: 0, + left: 0, + right: 0, + bottom: 0, +}; + +class BoxInspector extends React.Component { + render() { + var frame = this.props.frame; + var style = this.props.style; + var margin = style && resolveBoxStyle('margin', style) || blank; + var padding = style && resolveBoxStyle('padding', style) || blank; + return ( + + + + + ({frame.left}, {frame.top}) + + + {frame.width} × {frame.height} + + + + + ); + } +} + +class BoxContainer extends React.Component { + render() { + var box = this.props.box; + return ( + + + {this.props.title} + {box.top} + + + {box.left} + {this.props.children} + {box.right} + + {box.bottom} + + ); + } +} + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + marginLabel: { + width: 60, + }, + label: { + fontSize: 10, + color: 'rgb(255,100,0)', + marginLeft: 5, + flex: 1, + textAlign: 'left', + top: -3, + }, + buffer: { + fontSize: 10, + color: 'yellow', + flex: 1, + textAlign: 'center', + }, + innerText: { + color: 'yellow', + fontSize: 12, + textAlign: 'center', + width: 70, + }, + box: { + borderWidth: 1, + borderColor: 'grey', + }, + boxText: { + color: 'white', + fontSize: 12, + marginHorizontal: 3, + marginVertical: 2, + textAlign: 'center', + }, +}); + +module.exports = BoxInspector; + diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js index a94312b9c..a3851001c 100644 --- a/Libraries/ReactIOS/InspectorOverlay/ElementBox.js +++ b/Libraries/ReactIOS/InspectorOverlay/ElementBox.js @@ -60,13 +60,13 @@ var styles = StyleSheet.create({ position: 'absolute', }, content: { - backgroundColor: 'rgba(0, 0, 255, 0.1)', + backgroundColor: 'rgba(200, 230, 255, 0.8)', }, padding: { - borderColor: 'rgba(182, 217, 167, 0.3)', + borderColor: 'rgba(77, 255, 0, 0.3)', }, margin: { - borderColor: 'rgba(248, 194, 136, 0.3)', + borderColor: 'rgba(255, 132, 0, 0.3)', }, }); diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js new file mode 100644 index 000000000..9e9ae1d6a --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ElementProperties + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); +var PropTypes = require('ReactPropTypes'); +var BoxInspector = require('BoxInspector'); +var StyleInspector = require('StyleInspector'); + +var flattenStyle = require('flattenStyle'); + +var ElementProperties = React.createClass({ + propTypes: { + hierarchy: PropTypes.array.isRequired, + style: PropTypes.array.isRequired, + }, + render: function() { + var style = flattenStyle(this.props.style); + var path = this.props.hierarchy.map((instance) => { + return instance.getName ? instance.getName() : 'Unknown'; + }).join(' > '); + return ( + + + {path} + + + + + + + ); + } +}); + +var styles = StyleSheet.create({ + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + info: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 10, + }, + path: { + color: 'white', + fontSize: 9, + }, +}); + +module.exports = ElementProperties; diff --git a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index 97d08b90b..1b5009143 100644 --- a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -18,6 +18,7 @@ var Text = require('Text'); var UIManager = require('NativeModules').UIManager; var View = require('View'); var ElementBox = require('ElementBox'); +var ElementProperties = require('ElementProperties'); var InspectorOverlay = React.createClass({ getInitialState: function() { @@ -34,6 +35,9 @@ var InspectorOverlay = React.createClass({ [locationX, locationY], (nativeViewTag, left, top, width, height) => { var instance = Inspector.findInstanceByNativeTag(this.props.rootTag, nativeViewTag); + if (!instance) { + return; + } var hierarchy = Inspector.getOwnerHierarchy(instance); var publicInstance = instance.getPublicInstance(); this.setState({ @@ -52,18 +56,31 @@ var InspectorOverlay = React.createClass({ render: function() { var content = []; + var justifyContent = 'flex-end'; if (this.state.frame) { var distanceToTop = this.state.frame.top; var distanceToBottom = Dimensions.get('window').height - (this.state.frame.top + this.state.frame.height); - var justifyContent = distanceToTop > distanceToBottom + justifyContent = distanceToTop > distanceToBottom ? 'flex-start' : 'flex-end'; content.push(); - content.push(); + content.push( + + ); + } else { + content.push( + + Welcome to the inspector! Tap something to inspect it. + + ); } return ( { - return instance.getName ? instance.getName() : 'Unknown'; - }).join(' > '); - return ( - - - {path} - - - ); - } -}); - var styles = StyleSheet.create({ + welcomeMessage: { + backgroundColor: 'rgba(0, 0, 0, 0.7)', + padding: 10, + paddingVertical: 50, + }, + welcomeText: { + color: 'white', + }, inspector: { - backgroundColor: 'rgba(255,255,255,0.8)', + backgroundColor: 'rgba(255,255,255,0.0)', position: 'absolute', left: 0, top: 0, right: 0, bottom: 0, }, - info: { - backgroundColor: 'rgba(0, 0, 0, 0.7)', - padding: 10, - }, - path: { - color: 'white', - fontSize: 9, - } }); module.exports = InspectorOverlay; diff --git a/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js new file mode 100644 index 000000000..702d01e1d --- /dev/null +++ b/Libraries/ReactIOS/InspectorOverlay/StyleInspector.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule StyleInspector + * @flow + */ +'use strict'; + +var React = require('React'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var View = require('View'); + +class StyleInspector extends React.Component { + render() { + if (!this.props.style) { + return No style; + } + var names = Object.keys(this.props.style); + return ( + + + {names.map(name => {name}:)} + + + {names.map(name => {this.props.style[name]})} + + + ); + } +} + +var styles = StyleSheet.create({ + container: { + flexDirection: 'row', + }, + row: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + }, + attr: { + fontSize: 10, + color: '#ccc', + }, + value: { + fontSize: 10, + color: 'white', + marginLeft: 10, + }, + noStyle: { + color: 'white', + fontSize: 10, + }, +}); + +module.exports = StyleInspector; + diff --git a/Libraries/ReactIOS/resolveBoxStyle.js b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js similarity index 93% rename from Libraries/ReactIOS/resolveBoxStyle.js rename to Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js index 15342b56a..e0bfb601c 100644 --- a/Libraries/ReactIOS/resolveBoxStyle.js +++ b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js @@ -24,10 +24,10 @@ function resolveBoxStyle(prefix: String, style: Object): ?Object { var res = {}; var subs = ['top', 'left', 'bottom', 'right']; var set = false; + subs.forEach(sub => { + res[sub] = style[prefix] || 0; + }); if (style[prefix]) { - subs.forEach(sub => { - res[sub] = style[prefix]; - }); set = true; } if (style[prefix + 'Vertical']) { @@ -42,8 +42,6 @@ function resolveBoxStyle(prefix: String, style: Object): ?Object { var val = style[prefix + capFirst(sub)]; if (val) { res[sub] = val; - } - if (res[sub]) { set = true; } }); From 89a1e94a155ddf6290646e9ecb038db01941a10f Mon Sep 17 00:00:00 2001 From: Andrew Imm Date: Wed, 3 Jun 2015 14:11:20 -0700 Subject: [PATCH 10/25] Add an event for remote notification registration, and improve permissions request Summary: In order to add Push support to the Parse JS SDK in React Native, we need a way to receive the APNS device token from the JS context. This adds another event to PushNotificationIOS, so that code can respond to a successful registration. Additionally, I've updated the `requestPermissions` call to accept an optional map of parameters. This way, developers can request a subset of user notification types. Closes https://github.com/facebook/react-native/pull/1304 Github Author: Andrew Imm Test Plan: Imported from GitHub, without a `Test Plan:` line. --- .../PushNotificationIOS.js | 77 +++++++++++++++---- .../RCTPushNotificationManager.h | 1 + .../RCTPushNotificationManager.m | 77 ++++++++++++++----- 3 files changed, 117 insertions(+), 38 deletions(-) diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js index 4d03c6641..732d4c029 100644 --- a/Libraries/PushNotificationIOS/PushNotificationIOS.js +++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js @@ -20,6 +20,7 @@ var _initialNotification = RCTPushNotificationManager && RCTPushNotificationManager.initialNotification; var DEVICE_NOTIF_EVENT = 'remoteNotificationReceived'; +var NOTIF_REGISTER_EVENT = 'remoteNotificationsRegistered'; /** * Handle push notifications for your app, including permission handling and @@ -49,30 +50,72 @@ class PushNotificationIOS { } /** - * Attaches a listener to remote notifications while the app is running in the - * foreground or the background. + * Attaches a listener to remote notification events while the app is running + * in the foreground or the background. * - * The handler will get be invoked with an instance of `PushNotificationIOS` + * Valid events are: + * + * - `notification` : Fired when a remote notification is received. The + * handler will be invoked with an instance of `PushNotificationIOS`. + * - `register`: Fired when the user registers for remote notifications. The + * handler will be invoked with a hex string representing the deviceToken. */ static addEventListener(type: string, handler: Function) { invariant( - type === 'notification', - 'PushNotificationIOS only supports `notification` events' - ); - _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( - DEVICE_NOTIF_EVENT, - (notifData) => { - handler(new PushNotificationIOS(notifData)); - } + type === 'notification' || type === 'register', + 'PushNotificationIOS only supports `notification` and `register` events' ); + if (type === 'notification') { + _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( + DEVICE_NOTIF_EVENT, + (notifData) => { + handler(new PushNotificationIOS(notifData)); + } + ); + } else if (type === 'register') { + _notifHandlers[handler] = RCTDeviceEventEmitter.addListener( + NOTIF_REGISTER_EVENT, + (registrationInfo) => { + handler(registrationInfo.deviceToken); + } + ); + } } /** - * Requests all notification permissions from iOS, prompting the user's - * dialog box. + * Requests notification permissions from iOS, prompting the user's + * dialog box. By default, it will request all notification permissions, but + * a subset of these can be requested by passing a map of requested + * permissions. + * The following permissions are supported: + * + * - `alert` + * - `badge` + * - `sound` + * + * If a map is provided to the method, only the permissions with truthy values + * will be requested. */ - static requestPermissions() { - RCTPushNotificationManager.requestPermissions(); + static requestPermissions(permissions?: { + alert?: boolean, + badge?: boolean, + sound?: boolean + }) { + var requestedPermissions = {}; + if (permissions) { + requestedPermissions = { + alert: !!permissions.alert, + badge: !!permissions.badge, + sound: !!permissions.sound + }; + } else { + requestedPermissions = { + alert: true, + badge: true, + sound: true + }; + } + RCTPushNotificationManager.requestPermissions(requestedPermissions); } /** @@ -97,8 +140,8 @@ class PushNotificationIOS { */ static removeEventListener(type: string, handler: Function) { invariant( - type === 'notification', - 'PushNotificationIOS only supports `notification` events' + type === 'notification' || type === 'register', + 'PushNotificationIOS only supports `notification` and `register` events' ); if (!_notifHandlers[handler]) { return; diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h index ef1ba1496..194bbc5dd 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.h +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.h @@ -14,6 +14,7 @@ @interface RCTPushNotificationManager : NSObject + (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; ++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification; @end diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m index 4846c885e..1966c6045 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m @@ -12,7 +12,18 @@ #import "RCTBridge.h" #import "RCTEventDispatcher.h" +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + +#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert +#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge +#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound +#define UIUserNotificationTypeNone UIRemoteNotificationTypeNone +#define UIUserNotificationType UIRemoteNotificationType + +#endif + NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived"; +NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered"; @implementation RCTPushNotificationManager { @@ -30,6 +41,10 @@ RCT_EXPORT_MODULE() selector:@selector(handleRemoteNotificationReceived:) name:RCTRemoteNotificationReceived object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleRemoteNotificationsRegistered:) + name:RCTRemoteNotificationsRegistered + object:nil]; } return self; } @@ -52,6 +67,21 @@ RCT_EXPORT_MODULE() } } ++ (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + NSMutableString *hexString = [NSMutableString string]; + const unsigned char *bytes = [deviceToken bytes]; + for (int i = 0; i < [deviceToken length]; i++) { + [hexString appendFormat:@"%02x", bytes[i]]; + } + NSDictionary *userInfo = @{ + @"deviceToken" : [hexString copy] + }; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationsRegistered + object:self + userInfo:userInfo]; +} + + (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)notification { [[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived @@ -65,6 +95,12 @@ RCT_EXPORT_MODULE() body:[notification userInfo]]; } +- (void)handleRemoteNotificationsRegistered:(NSNotification *)notification +{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"remoteNotificationsRegistered" + body:[notification userInfo]]; +} + /** * Update the application icon badge number on the home screen */ @@ -83,36 +119,35 @@ RCT_EXPORT_METHOD(getApplicationIconBadgeNumber:(RCTResponseSenderBlock)callback ]); } -RCT_EXPORT_METHOD(requestPermissions) +RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions) { - Class _UIUserNotificationSettings; - if ((_UIUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings"))) { - UIUserNotificationType types = UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert; - UIUserNotificationSettings *notificationSettings = [_UIUserNotificationSettings settingsForTypes:types categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; + UIUserNotificationType types = UIRemoteNotificationTypeNone; + if (permissions) { + if ([permissions[@"alert"] boolValue]) { + types |= UIUserNotificationTypeAlert; + } + if ([permissions[@"badge"] boolValue]) { + types |= UIUserNotificationTypeBadge; + } + if ([permissions[@"sound"] boolValue]) { + types |= UIUserNotificationTypeSound; + } } else { + types = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound; + } -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - - [[UIApplication sharedApplication] registerForRemoteNotificationTypes: - UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert]; - +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 + id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; +#else + [[UIApplication sharedApplication] registerForRemoteNotificationTypes:types]; #endif - } } RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback) { - -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 - -#define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert -#define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge -#define UIUserNotificationTypeSound UIRemoteNotificationTypeSound - -#endif - NSUInteger types = 0; if ([UIApplication instancesRespondToSelector:@selector(currentUserNotificationSettings)]) { types = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; From 856469a24b6f0b0998061c1244533b510aee3ddd Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Wed, 3 Jun 2015 14:12:03 -0700 Subject: [PATCH 11/25] [react-packager] Support packages with '.' in the name Summary: @public Fixes issue #1055 For some historical reason we used to strip the extension of the module name before passing it to `resolveDependency` which is completly capable of handling all kinds of names. The fix is one line, but added a few tests for this. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PacakgerIntegration * Open app and click around --- .../__tests__/DependencyGraph-test.js | 131 ++++++++++++++++++ .../haste/DependencyGraph/index.js | 3 +- 2 files changed, 132 insertions(+), 2 deletions(-) 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 6c0fd05f6..4dce3b8a4 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 @@ -577,6 +577,80 @@ describe('DependencyGraph', function() { }); }); + pit('should work with packages with a dot in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + 'require("x.y.z")', + ].join('\n'), + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + }, + 'x.y.z': { + 'package.json': JSON.stringify({ + name: 'x.y.z', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js', 'x.y.z'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'x.y.z/main', + path: '/root/x.y.z/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should default main package to index.js', function() { var root = '/root'; fs.__setMockFilesystem({ @@ -2116,6 +2190,63 @@ describe('DependencyGraph', function() { ]); }); }); + + pit('should work with node packages with a .js in the name', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("sha.js")', + ].join('\n'), + 'node_modules': { + 'sha.js': { + 'package.json': JSON.stringify({ + name: 'sha.js', + main: 'main.js' + }), + 'main.js': 'lol' + } + } + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'index', + altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['sha.js'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: 'sha.js/main', + path: '/root/node_modules/sha.js/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); }); describe('file watch updating', function() { diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index fc899cb2f..74c131276 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -144,8 +144,7 @@ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { function iter(mod) { var p = Promise.resolve(); mod.dependencies.forEach(function(name) { - var id = sansExtJs(name); - var dep = self.resolveDependency(mod, id); + var dep = self.resolveDependency(mod, name); if (dep == null) { debug( From 6d25ff83ec2b55869342d5b23d559e453348a34f Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Wed, 3 Jun 2015 16:07:39 -0700 Subject: [PATCH 12/25] [react_native] Fix sync --- .../Components/ProgressViewIOS/ProgressViewIOS.android.js | 6 +++--- .../SegmentedControlIOS/SegmentedControlIOS.android.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js index b9103c667..abda1c368 100644 --- a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js +++ b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js @@ -7,7 +7,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule SegmentedControlIOS + * @providesModule ProgressViewIOS */ 'use strict'; @@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); -var Dummy = React.createClass({ +var DummyProgressViewIOS = React.createClass({ render: function() { return ( @@ -46,4 +46,4 @@ var styles = StyleSheet.create({ } }); -module.exports = Dummy; +module.exports = DummyProgressViewIOS; diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js index 28fbea027..848144bff 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js @@ -17,7 +17,7 @@ var StyleSheet = require('StyleSheet'); var Text = require('Text'); var View = require('View'); -var Dummy = React.createClass({ +var DummySegmentedControlIOS = React.createClass({ render: function() { return ( @@ -46,4 +46,4 @@ var styles = StyleSheet.create({ } }); -module.exports = Dummy; +module.exports = DummySegmentedControlIOS; From fa3491e9f52c4ad7374d71f9a061de12be23f10e Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Wed, 3 Jun 2015 16:15:29 -0700 Subject: [PATCH 13/25] [ReactNative] Remove unused touch arrays Summary: These two arrays aren't used. The code is easier to read without them @public Test Plan: Tap around UIExplorer and verify multi-touch and responder system behavior works the same --- React/Base/RCTTouchHandler.m | 5 ----- 1 file changed, 5 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index f95f134c3..4ffef7ee7 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -36,8 +36,6 @@ BOOL _recordingInteractionTiming; CFTimeInterval _mostRecentEnqueueJS; - NSMutableArray *_pendingTouches; - NSMutableArray *_bridgeInteractionTiming; } - (instancetype)initWithBridge:(RCTBridge *)bridge @@ -52,9 +50,6 @@ _reactTouches = [[NSMutableArray alloc] init]; _touchViews = [[NSMutableArray alloc] init]; - _pendingTouches = [[NSMutableArray alloc] init]; - _bridgeInteractionTiming = [[NSMutableArray alloc] init]; - // `cancelsTouchesInView` is needed in order to be used as a top level // event delegated recognizer. Otherwise, lower-level components not built // using RCT, will fail to recognize gestures. From 63b826284331fb16d6ebcfd4e7ed4340f85880f7 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 3 Jun 2015 16:39:20 -0700 Subject: [PATCH 14/25] [ReactNative] Allow TouchableWithoutFeedback override accessible attribute --- Libraries/Components/Touchable/TouchableWithoutFeedback.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/Touchable/TouchableWithoutFeedback.js b/Libraries/Components/Touchable/TouchableWithoutFeedback.js index 9449f79bb..227cbeae2 100755 --- a/Libraries/Components/Touchable/TouchableWithoutFeedback.js +++ b/Libraries/Components/Touchable/TouchableWithoutFeedback.js @@ -40,6 +40,7 @@ var TouchableWithoutFeedback = React.createClass({ * Called when the touch is released, but not if cancelled (e.g. by a scroll * that steals the responder lock). */ + accessible: React.PropTypes.bool, onPress: React.PropTypes.func, onPressIn: React.PropTypes.func, onPressOut: React.PropTypes.func, @@ -110,7 +111,7 @@ var TouchableWithoutFeedback = React.createClass({ render: function(): ReactElement { // Note(avik): remove dynamic typecast once Flow has been upgraded return (React: any).cloneElement(onlyChild(this.props.children), { - accessible: true, + accessible: this.props.accessible !== false, testID: this.props.testID, onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder, onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest, From 9fe71284935776e76a9d50090440f8b1adddc0bc Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Wed, 3 Jun 2015 16:40:14 -0700 Subject: [PATCH 15/25] [ReactNative] Refactor DevMenu items construction Summary: The idea behind this change it to couple together menu item title and handler. The code becomes simpler and easier to maintain, but also makes it possible to extend dev menu in the future. @public Test Plan: All menu items works as before. Changed websocket executor class name and made sure that when the class is missing we get nice error message. --- React/Base/RCTDevMenu.h | 6 ++ React/Base/RCTDevMenu.m | 174 ++++++++++++++++++++++++---------------- 2 files changed, 112 insertions(+), 68 deletions(-) diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h index b260fca4a..ed1ff90b8 100644 --- a/React/Base/RCTDevMenu.h +++ b/React/Base/RCTDevMenu.h @@ -51,6 +51,12 @@ */ - (void)reload; +/** + * Add custom item to the development menu. The handler will be called + * when user selects the item. + */ +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler; + @end /** diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 18de3a457..8a5f23f9d 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -43,6 +43,28 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu"; @end +@interface RCTDevMenuItem : NSObject + +@property (nonatomic, copy) NSString *title; +@property (nonatomic, copy) dispatch_block_t handler; + +- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler; + +@end + +@implementation RCTDevMenuItem + +- (instancetype)initWithTitle:(NSString *)title handler:(dispatch_block_t)handler +{ + if (self = [super init]) { + self.title = title; + self.handler = handler; + } + return self; +} + +@end + @interface RCTDevMenu () @property (nonatomic, strong) Class executorClass; @@ -57,6 +79,8 @@ static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu"; NSURLSessionDataTask *_updateTask; NSURL *_liveReloadURL; BOOL _jsLoaded; + NSArray *_presentedItems; + NSMutableArray *_extraMenuItems; } @synthesize bridge = _bridge; @@ -94,6 +118,7 @@ RCT_EXPORT_MODULE() _defaults = [NSUserDefaults standardUserDefaults]; _settings = [[NSMutableDictionary alloc] init]; + _extraMenuItems = [NSMutableArray array]; // Delay setup until after Bridge init [self settingsDidChange]; @@ -232,32 +257,82 @@ RCT_EXPORT_MODULE() } } +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler +{ + [_extraMenuItems addObject:[[RCTDevMenuItem alloc] initWithTitle:title handler:handler]]; +} + +- (NSArray *)menuItems +{ + NSMutableArray *items = [NSMutableArray array]; + + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Reload" handler:^{ + [self reload]; + }]]; + + Class chromeExecutorClass = NSClassFromString(@"RCTWebSocketExecutor"); + if (!chromeExecutorClass) { + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Chrome Debugger Unavailable" handler:^{ + [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" + message:@"You need to include the RCTWebSocket library to enable Chrome debugging" + delegate:nil + cancelButtonTitle:@"OK" + otherButtonTitles:nil] show]; + }]]; + } else { + BOOL isDebuggingInChrome = _executorClass && _executorClass == chromeExecutorClass; + NSString *debugTitleChrome = isDebuggingInChrome ? @"Disable Chrome Debugging" : @"Debug in Chrome"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleChrome handler:^{ + self.executorClass = isDebuggingInChrome ? Nil : chromeExecutorClass; + }]]; + } + + Class safariExecutorClass = NSClassFromString(@"RCTWebViewExecutor"); + BOOL isDebuggingInSafari = _executorClass && _executorClass == safariExecutorClass; + NSString *debugTitleSafari = isDebuggingInSafari ? @"Disable Safari Debugging" : @"Debug in Safari"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:debugTitleSafari handler:^{ + self.executorClass = isDebuggingInSafari ? Nil : safariExecutorClass; + }]]; + + NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:fpsMonitor handler:^{ + self.showFPS = !_showFPS; + }]]; + + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:@"Inspect Element" handler:^{ + [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; + }]]; + + if (_liveReloadURL) { + NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:liveReloadTitle handler:^{ + self.liveReloadEnabled = !_liveReloadEnabled; + }]]; + + NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling"; + [items addObject:[[RCTDevMenuItem alloc] initWithTitle:profilingTitle handler:^{ + self.profilingEnabled = !_profilingEnabled; + }]]; + } + + [items addObjectsFromArray:_extraMenuItems]; + + return items; +} + RCT_EXPORT_METHOD(show) { if (_actionSheet || !_bridge) { return; } - NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome"; - NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari"; - NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor"; + UIActionSheet *actionSheet = [[UIActionSheet alloc] init]; + actionSheet.title = @"React Native: Development"; + actionSheet.delegate = self; - UIActionSheet *actionSheet = - [[UIActionSheet alloc] initWithTitle:@"React Native: Development" - delegate:self - cancelButtonTitle:nil - destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil]; - - [actionSheet addButtonWithTitle:@"Inspect Element"]; - - if (_liveReloadURL) { - - NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; - NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling"; - - [actionSheet addButtonWithTitle:liveReloadTitle]; - [actionSheet addButtonWithTitle:profilingTitle]; + NSArray *items = [self menuItems]; + for (RCTDevMenuItem *item in items) { + [actionSheet addButtonWithTitle:item.title]; } [actionSheet addButtonWithTitle:@"Cancel"]; @@ -266,13 +341,7 @@ RCT_EXPORT_METHOD(show) actionSheet.actionSheetStyle = UIBarStyleBlack; [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; _actionSheet = actionSheet; -} - -RCT_EXPORT_METHOD(reload) -{ - _jsLoaded = NO; - _liveReloadURL = nil; - [_bridge reload]; + _presentedItems = items; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex @@ -282,48 +351,16 @@ RCT_EXPORT_METHOD(reload) return; } - switch (buttonIndex) { - case 0: { - [self reload]; - break; - } - case 1: { - Class cls = NSClassFromString(@"RCTWebSocketExecutor"); - if (!cls) { - [[[UIAlertView alloc] initWithTitle:@"Chrome Debugger Unavailable" - message:@"You need to include the RCTWebSocket library to enable Chrome debugging" - delegate:nil - cancelButtonTitle:@"OK" - otherButtonTitles:nil] show]; - return; - } - self.executorClass = (_executorClass == cls) ? Nil : cls; - break; - } - case 2: { - Class cls = NSClassFromString(@"RCTWebViewExecutor"); - self.executorClass = (_executorClass == cls) ? Nil : cls; - break; - } - case 3: { - self.showFPS = !_showFPS; - break; - } - case 4: { - [_bridge.eventDispatcher sendDeviceEventWithName:@"toggleElementInspector" body:nil]; - break; - } - case 5: { - self.liveReloadEnabled = !_liveReloadEnabled; - break; - } - case 6: { - self.profilingEnabled = !_profilingEnabled; - break; - } - default: - break; - } + RCTDevMenuItem *item = _presentedItems[buttonIndex]; + item.handler(); + return; +} + +RCT_EXPORT_METHOD(reload) +{ + _jsLoaded = NO; + _liveReloadURL = nil; + [_bridge reload]; } - (void)setShakeToShow:(BOOL)shakeToShow @@ -445,6 +482,7 @@ RCT_EXPORT_METHOD(reload) - (void)show {} - (void)reload {} +- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {} @end From 7ffa7bd1f101166fce2cc8016df405b2fb67d4aa Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Wed, 3 Jun 2015 16:57:08 -0700 Subject: [PATCH 16/25] [ReactNative] Implement merge functionality for AsyncStorage --- IntegrationTests/AsyncStorageTest.js | 49 ++++++++++++++----- React/Base/RCTUtils.h | 2 + React/Base/RCTUtils.m | 12 ++++- React/Modules/RCTAsyncLocalStorage.m | 73 ++++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js index 6d13bb6e9..911887d3e 100644 --- a/IntegrationTests/AsyncStorageTest.js +++ b/IntegrationTests/AsyncStorageTest.js @@ -16,12 +16,19 @@ var { View, } = React; +var deepDiffer = require('deepDiffer'); + var DEBUG = false; var KEY_1 = 'key_1'; var VAL_1 = 'val_1'; var KEY_2 = 'key_2'; var VAL_2 = 'val_2'; +var KEY_MERGE = 'key_merge'; +var VAL_MERGE_1 = {'foo': 1, 'bar': {'hoo': 1, 'boo': 1}, 'moo': {'a': 3}}; +var VAL_MERGE_2 = {'bar': {'hoo': 2}, 'baz': 2, 'moo': {'a': 3}}; +var VAL_MERGE_EXPECT = + {'foo': 1, 'bar': {'hoo': 2, 'boo': 1}, 'baz': 2, 'moo': {'a': 3}}; // setup in componentDidMount var done; @@ -40,8 +47,9 @@ function expectTrue(condition, message) { function expectEqual(lhs, rhs, testname) { expectTrue( - lhs === rhs, - 'Error in test ' + testname + ': expected ' + rhs + ', got ' + lhs + !deepDiffer(lhs, rhs), + 'Error in test ' + testname + ': expected\n' + JSON.stringify(rhs) + + '\ngot\n' + JSON.stringify(lhs) ); } @@ -93,25 +101,25 @@ function testRemoveItem() { 'Missing KEY_1 or KEY_2 in ' + '(' + result + ')' ); updateMessage('testRemoveItem - add two items'); - AsyncStorage.removeItem(KEY_1, (err) => { - expectAsyncNoError(err); + AsyncStorage.removeItem(KEY_1, (err2) => { + expectAsyncNoError(err2); updateMessage('delete successful '); - AsyncStorage.getItem(KEY_1, (err, result) => { - expectAsyncNoError(err); + AsyncStorage.getItem(KEY_1, (err3, result2) => { + expectAsyncNoError(err3); expectEqual( - result, + result2, null, 'testRemoveItem: key_1 present after delete' ); updateMessage('key properly removed '); - AsyncStorage.getAllKeys((err, result2) => { - expectAsyncNoError(err); + AsyncStorage.getAllKeys((err4, result3) => { + expectAsyncNoError(err4); expectTrue( - result2.indexOf(KEY_1) === -1, - 'Unexpected: KEY_1 present in ' + result2 + result3.indexOf(KEY_1) === -1, + 'Unexpected: KEY_1 present in ' + result3 ); - updateMessage('proper length returned.\nDone!'); - done(); + updateMessage('proper length returned.'); + runTestCase('should merge values', testMerge); }); }); }); @@ -120,6 +128,21 @@ function testRemoveItem() { }); } +function testMerge() { + AsyncStorage.setItem(KEY_MERGE, JSON.stringify(VAL_MERGE_1), (err1) => { + expectAsyncNoError(err1); + AsyncStorage.mergeItem(KEY_MERGE, JSON.stringify(VAL_MERGE_2), (err2) => { + expectAsyncNoError(err2); + AsyncStorage.getItem(KEY_MERGE, (err3, result) => { + expectAsyncNoError(err3); + expectEqual(JSON.parse(result), VAL_MERGE_EXPECT, 'testMerge'); + updateMessage('objects deeply merged\nDone!'); + done(); + }); + }); + }); +} + var AsyncStorageTest = React.createClass({ getInitialState() { return { diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 5c34d0e0a..641500b38 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -18,6 +18,8 @@ // Utility functions for JSON object <-> string serialization/deserialization RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); +RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error); +RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options); // Strip non JSON-safe values from an object graph RCT_EXTERN id RCTJSONClean(id object); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 712e9724e..613b13163 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -24,7 +24,7 @@ NSString *RCTJSONStringify(id jsonObject, NSError **error) return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil; } -id RCTJSONParse(NSString *jsonString, NSError **error) +id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJSONReadingOptions options) { if (!jsonString) { return nil; @@ -39,7 +39,15 @@ id RCTJSONParse(NSString *jsonString, NSError **error) return nil; } } - return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error]; + return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error]; +} + +id RCTJSONParse(NSString *jsonString, NSError **error) { + return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingAllowFragments); +} + +id RCTJSONParseMutable(NSString *jsonString, NSError **error) { + return RCTJSONParseWithOptions(jsonString, error, NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves); } id RCTJSONClean(id object) diff --git a/React/Modules/RCTAsyncLocalStorage.m b/React/Modules/RCTAsyncLocalStorage.m index 2c01161d4..76f7fa885 100644 --- a/React/Modules/RCTAsyncLocalStorage.m +++ b/React/Modules/RCTAsyncLocalStorage.m @@ -61,6 +61,34 @@ static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut return nil; } +// Only merges objects - all other types are just clobbered (including arrays) +static void RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source) +{ + for (NSString *key in source) { + id sourceValue = source[key]; + if ([sourceValue isKindOfClass:[NSDictionary class]]) { + id destinationValue = destination[key]; + NSMutableDictionary *nestedDestination; + if ([destinationValue classForCoder] == [NSMutableDictionary class]) { + nestedDestination = destinationValue; + } else { + if ([destinationValue isKindOfClass:[NSDictionary class]]) { + // Ideally we wouldn't eagerly copy here... + nestedDestination = [destinationValue mutableCopy]; + } else { + destination[key] = [sourceValue copy]; + } + } + if (nestedDestination) { + RCTMergeRecursive(nestedDestination, sourceValue); + destination[key] = nestedDestination; + } + } else { + destination[key] = sourceValue; + } + } +} + #pragma mark - RCTAsyncLocalStorage @implementation RCTAsyncLocalStorage @@ -135,13 +163,19 @@ RCT_EXPORT_MODULE() if (errorOut) { return errorOut; } + id value = [self _getValueForKey:key errorOut:&errorOut]; + [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure. + return errorOut; +} + +- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut +{ id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value. if (value == [NSNull null]) { NSString *filePath = [self _filePathForKey:key]; - value = RCTReadFile(filePath, key, &errorOut); + value = RCTReadFile(filePath, key, errorOut); } - [result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure. - return errorOut; + return value; } - (id)_writeEntry:(NSArray *)entry @@ -198,7 +232,6 @@ RCT_EXPORT_METHOD(multiGet:(NSArray *)keys id keyError = [self _appendItemForKey:key toArray:result]; RCTAppendError(keyError, &errors); } - [self _writeManifest:&errors]; callback(@[errors ?: [NSNull null], result]); } @@ -221,6 +254,38 @@ RCT_EXPORT_METHOD(multiSet:(NSArray *)kvPairs } } +RCT_EXPORT_METHOD(multiMerge:(NSArray *)kvPairs + callback:(RCTResponseSenderBlock)callback) +{ + id errorOut = [self _ensureSetup]; + if (errorOut) { + callback(@[@[errorOut]]); + return; + } + NSMutableArray *errors; + for (__strong NSArray *entry in kvPairs) { + id keyError; + NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError]; + if (keyError) { + RCTAppendError(keyError, &errors); + } else { + if (value) { + NSMutableDictionary *mergedVal = [RCTJSONParseMutable(value, &keyError) mutableCopy]; + RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &keyError)); + entry = @[entry[0], RCTJSONStringify(mergedVal, &keyError)]; + } + if (!keyError) { + keyError = [self _writeEntry:entry]; + } + RCTAppendError(keyError, &errors); + } + } + [self _writeManifest:&errors]; + if (callback) { + callback(@[errors ?: [NSNull null]]); + } +} + RCT_EXPORT_METHOD(multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback) { From 015b5cf8e5123d4f6685058919bd177a3a169acb Mon Sep 17 00:00:00 2001 From: Alex Madjar Date: Wed, 3 Jun 2015 17:47:24 -0700 Subject: [PATCH 17/25] [React Native] PickerIOS can update its items list Summary: @public PickerIOS doesn't look at the content of its list. It just keeps a list ref and pushes new items in. Sadly this doesn't work with the naive reference checker so new item lists don't trigger a native rerender. The solution here is to ask PickerIOS to just pass its props down to native directly Test Plan: Implemented a simple Picker hooked up to a store getting new items. Watched the list not update and then update after this diff as new items come in --- Libraries/Picker/PickerIOS.ios.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index b2c3c3927..b1b1d06f4 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -20,8 +20,7 @@ var RCTPickerIOSConsts = require('NativeModules').UIManager.RCTPicker.Constants; var StyleSheet = require('StyleSheet'); var View = require('View'); -var createReactNativeComponentClass = - require('createReactNativeComponentClass'); +var requireNativeComponent = require('requireNativeComponent'); var merge = require('merge'); var PICKER = 'picker'; @@ -112,14 +111,6 @@ var styles = StyleSheet.create({ }, }); -var rkPickerIOSAttributes = merge(ReactNativeViewAttributes.UIView, { - items: true, - selectedIndex: true, -}); - -var RCTPickerIOS = createReactNativeComponentClass({ - validAttributes: rkPickerIOSAttributes, - uiViewClassName: 'RCTPicker', -}); +var RCTPickerIOS = requireNativeComponent('RCTPicker', null); module.exports = PickerIOS; From 53b2c39cc08270bfd94df655d3d3271066769d83 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Thu, 4 Jun 2015 10:27:51 -0700 Subject: [PATCH 18/25] select up and down the inspector hierarchy Summary: This allows you to select the displayed owner hierarchy, and see the styles, props, and position. @public Test Plan: Open the inspector, select something in the middle of the page. Click the breadcrumb train in the inspector, and verify that: - styles are reflected - margin/padding/box is correct - the highlight updates to show the selected item See video as well. [Video](https://www.latest.facebook.com/pxlcld/mqnl) Screenshot {F22518618} --- .../InspectorOverlay/ElementProperties.js | 65 +++++++++++++++---- .../InspectorOverlay/InspectorOverlay.js | 23 ++++++- Libraries/Utilities/mapWithSeparator.js | 19 ++++++ 3 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 Libraries/Utilities/mapWithSeparator.js diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js index 9e9ae1d6a..310374fb1 100644 --- a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js +++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js @@ -18,38 +18,79 @@ var View = require('View'); var PropTypes = require('ReactPropTypes'); var BoxInspector = require('BoxInspector'); var StyleInspector = require('StyleInspector'); +var TouchableHighlight = require('TouchableHighlight'); +var TouchableWithoutFeedback = require('TouchableWithoutFeedback'); var flattenStyle = require('flattenStyle'); +var mapWithSeparator = require('mapWithSeparator'); var ElementProperties = React.createClass({ propTypes: { hierarchy: PropTypes.array.isRequired, style: PropTypes.array.isRequired, }, + render: function() { var style = flattenStyle(this.props.style); - var path = this.props.hierarchy.map((instance) => { - return instance.getName ? instance.getName() : 'Unknown'; - }).join(' > '); + var selection = this.props.selection; + // Without the `TouchableWithoutFeedback`, taps on this inspector pane + // would change the inspected element to whatever is under the inspector return ( - - - {path} - - - - + + + + {mapWithSeparator( + this.props.hierarchy, + (item, i) => ( + this.props.setSelection(i)}> + + {item.getName ? item.getName() : 'Unknown'} + + + ), + () => + )} + + + + + - + ); } }); var styles = StyleSheet.create({ + breadSep: { + fontSize: 8, + color: 'white', + }, + breadcrumb: { + flexDirection: 'row', + flexWrap: 'wrap', + marginBottom: 5, + }, + selected: { + borderColor: 'white', + borderRadius: 5, + }, + breadItem: { + borderWidth: 1, + borderColor: 'transparent', + marginHorizontal: 2, + }, + breadItemText: { + fontSize: 10, + color: 'white', + marginHorizontal: 5, + }, row: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'space-around', + justifyContent: 'space-between', }, info: { backgroundColor: 'rgba(0, 0, 0, 0.7)', diff --git a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js index 1b5009143..3b391502f 100644 --- a/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js @@ -24,7 +24,9 @@ var InspectorOverlay = React.createClass({ getInitialState: function() { return { frame: null, + pointerY: 0, hierarchy: [], + selection: -1, }; }, @@ -42,6 +44,8 @@ var InspectorOverlay = React.createClass({ var publicInstance = instance.getPublicInstance(); this.setState({ hierarchy, + pointerY: locationY, + selection: hierarchy.length - 1, frame: {left, top, width, height}, style: publicInstance.props ? publicInstance.props.style : {}, }); @@ -49,6 +53,18 @@ var InspectorOverlay = React.createClass({ ); }, + setSelection(i) { + var instance = this.state.hierarchy[i]; + var publicInstance = instance.getPublicInstance(); + UIManager.measure(React.findNodeHandle(instance), (x, y, width, height, left, top) => { + this.setState({ + frame: {left, top, width, height}, + style: publicInstance.props ? publicInstance.props.style : {}, + selection: i, + }); + }); + }, + shouldSetResponser: function(e) { this.findViewForTouchEvent(e); return true; @@ -59,9 +75,8 @@ var InspectorOverlay = React.createClass({ var justifyContent = 'flex-end'; if (this.state.frame) { - var distanceToTop = this.state.frame.top; - var distanceToBottom = Dimensions.get('window').height - - (this.state.frame.top + this.state.frame.height); + var distanceToTop = this.state.pointerY; + var distanceToBottom = Dimensions.get('window').height - distanceToTop; justifyContent = distanceToTop > distanceToBottom ? 'flex-start' @@ -73,6 +88,8 @@ var InspectorOverlay = React.createClass({ style={this.state.style} frame={this.state.frame} hierarchy={this.state.hierarchy} + selection={this.state.selection} + setSelection={this.setSelection} /> ); } else { diff --git a/Libraries/Utilities/mapWithSeparator.js b/Libraries/Utilities/mapWithSeparator.js new file mode 100644 index 000000000..4aa8665c3 --- /dev/null +++ b/Libraries/Utilities/mapWithSeparator.js @@ -0,0 +1,19 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @providesModule mapWithSeparator + */ +'use strict'; + +function mapWithSeparator(array, valueFunction, separatorFunction) { + var results = []; + for (var i = 0; i < array.length; i++) { + results.push(valueFunction(array[i], i, array)); + if (i !== array.length - 1) { + results.push(separatorFunction(i)); + } + } + return results; +} + +module.exports = mapWithSeparator; From 30fc7389d18e7e5c65f29556fade37188b70f22f Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 4 Jun 2015 15:09:13 -0700 Subject: [PATCH 19/25] [react-packager] Fix more node_modules resolution rules Summary: @public Fixes #773 This fixes `.json` name resolution. And also reads `package.json` when doing a directory module resolution. The algorithm can be found here: https://nodejs.org/api/modules.html I'll probably start including the node (or browserify) modules test in later diffs to make sure we're fully compliant. Test Plan: * ./runJestTests.sh * ./runJestTests.sh PackagerIntegration * open playground and require a json file * test redbox --- .../__tests__/DependencyGraph-test.js | 69 ++++++++++++++++++- .../haste/DependencyGraph/index.js | 35 ++++------ 2 files changed, 80 insertions(+), 24 deletions(-) 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 4dce3b8a4..935c4e308 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 @@ -168,9 +168,11 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")' + 'require("./a.json")', + 'require("./b")' ].join('\n'), 'a.json': JSON.stringify({}), + 'b.json': JSON.stringify({}), } }); @@ -186,7 +188,7 @@ describe('DependencyGraph', function() { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json'], + dependencies: ['./a.json', './b'], isAsset: false, isAsset_DEPRECATED: false, isJSON: undefined, @@ -205,6 +207,17 @@ describe('DependencyGraph', function() { resolution: undefined, resolveDependency: undefined, }, + { + id: 'package/b.json', + isJSON: true, + path: '/root/b.json', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, ]); }); }); @@ -851,6 +864,58 @@ describe('DependencyGraph', function() { }); }); + pit('should resolve require to main if it is a dir w/ a package.json', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'package.json': JSON.stringify({ + name: 'test', + }), + 'index.js': 'require("./lib/")', + lib: { + 'package.json': JSON.stringify({ + 'main': 'main.js', + }), + 'index.js': 'lol', + 'main.js': 'lol', + }, + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + }); + return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { + expect(getDataFromModules(deps)) + .toEqual([ + { + id: 'test/index', + path: '/root/index.js', + dependencies: ['./lib/'], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + { + id: '/root/lib/main.js', + path: '/root/lib/main.js', + dependencies: [], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: undefined, + isPolyfill: false, + resolution: undefined, + resolveDependency: undefined, + }, + ]); + }); + }); + pit('should ignore malformed packages', 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 74c131276..1aa8b1290 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -1,6 +1,3 @@ -// TODO -// Fix it to work with tests - /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. @@ -296,16 +293,18 @@ DependecyGraph.prototype.resolveDependency = function( modulePath = path.join(dir, depModuleId); modulePath = browserFieldRedirect(packageJson, modulePath); - // JS modules can be required without extensios. - if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { - modulePath = withExtJs(modulePath); - } + dep = this._graph[modulePath] || + this._graph[modulePath + '.js'] || + this._graph[modulePath + '.json']; - dep = this._graph[modulePath]; - - // Maybe the dependency is a directory and there is an index.js inside it. + // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. if (dep == null) { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; + if (dirPackageJson) { + dep = this._resolvePackageMain(dirPackageJson); + } else { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } } // Maybe it's an asset with @n.nx resolution and the path doesn't map @@ -444,14 +443,6 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } - if (packageJson.name == null) { - debug( - 'WARNING: package.json `%s` is missing a name field', - packagePath - ); - return Promise.resolve(); - } - packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -461,14 +452,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { this._packagesById[packageJson.name] = packageJson; } }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root)) { + if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { delete this._packagesById[packageJson.name]; } }; @@ -536,7 +527,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null) { + if (packageJson == null || packageJson.name == null) { return path.resolve(modulePath); } else { var relativePath = From f744c7c4440ce8c16a45f8cc8fd79255c4283ba4 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 4 Jun 2015 20:20:37 -0700 Subject: [PATCH 20/25] [ReactNative] Native touch unique identifier Summary: This bug was causing a redbox when touching rapidly because multiple touches had the same identifier. This code was meant to ensure that a unique ID is found, but it was checking against the wrong property in the react touch array. I don't think this was really affecting prod, because the invariant is guarded by a __DEV__ check @public Test Plan: - Start several touches at once (or just rapidly jam fingers on your device), and you won't see a redbox - Also verify that single touches, touch moves, and multi touches work as before --- React/Base/RCTTouchHandler.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index 4ffef7ee7..0a2ca61a7 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -89,11 +89,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) { return; } - // Get new, unique touch id + // Get new, unique touch identifier for the react touch const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices - NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches; + NSInteger touchID = ([_reactTouches.lastObject[@"identifier"] integerValue] + 1) % RCTMaxTouches; for (NSDictionary *reactTouch in _reactTouches) { - NSInteger usedID = [reactTouch[@"target"] integerValue]; + NSInteger usedID = [reactTouch[@"identifier"] integerValue]; if (usedID == touchID) { // ID has already been used, try next value touchID ++; From 7009f0a47bee3f41e344605cc272360248ce863c Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 5 Jun 2015 04:23:51 -0700 Subject: [PATCH 21/25] [ReactNative] Add profiling hooks to bridge modules at runtime Summary: @public Add `RCT_DEV`-only profiling hooks to every method in every bridge modules to add a little bit more detail to the timeline profile. Test Plan: {F22522834} --- React/Base/RCTBridge.m | 51 ++++++++++------- React/Base/RCTProfile.h | 6 +- React/Base/RCTProfile.m | 118 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 25 deletions(-) diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 70868f721..c28778c49 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1313,7 +1313,12 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin #pragma mark - Payload Generation -- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID +- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module +{ + [self dispatchBlock:block forModuleID:RCTModuleIDsByName[RCTBridgeModuleNameForClass([module class])]]; +} + +- (void)dispatchBlock:(dispatch_block_t)block forModuleID:(NSNumber *)moduleID { RCTAssertJSThread(); @@ -1458,7 +1463,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin if ([module respondsToSelector:@selector(batchDidComplete)]) { [self dispatchBlock:^{ [module batchDidComplete]; - } forModule:moduleID]; + } forModuleID:moduleID]; } }]; } @@ -1526,7 +1531,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin @"selector": NSStringFromSelector(method.selector), @"args": RCTJSONStringify(params ?: [NSNull null], NULL), }); - } forModule:@(moduleID)]; + } forModuleID:@(moduleID)]; return YES; } @@ -1546,7 +1551,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin RCTProfileBeginEvent(); [observer didUpdateFrame:frameUpdate]; RCTProfileEndEvent(name, @"objc_call,fps", nil); - } forModule:RCTModuleIDsByName[RCTBridgeModuleNameForClass([observer class])]]; + } forModule:(id)observer]; } } @@ -1591,35 +1596,39 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (void)startProfiling { - RCTAssertMainThread(); + RCTAssertMainThread(); if (![_parentBridge.bundleURL.scheme isEqualToString:@"http"]) { RCTLogError(@"To run the profiler you must be running from the dev server"); return; } - RCTProfileInit(); + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + RCTProfileInit(self); + }]; } - (void)stopProfiling { RCTAssertMainThread(); - NSString *log = RCTProfileEnd(); - NSURL *bundleURL = _parentBridge.bundleURL; - NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port]; - NSURL *URL = [NSURL URLWithString:URLString]; - NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; - URLRequest.HTTPMethod = @"POST"; - [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest - fromData:[log dataUsingEncoding:NSUTF8StringEncoding] - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - RCTLogError(@"%@", error.localizedDescription); - } - }]; - [task resume]; + [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ + NSString *log = RCTProfileEnd(self); + NSURL *bundleURL = _parentBridge.bundleURL; + NSString *URLString = [NSString stringWithFormat:@"%@://%@:%@/profile", bundleURL.scheme, bundleURL.host, bundleURL.port]; + NSURL *URL = [NSURL URLWithString:URLString]; + NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL]; + URLRequest.HTTPMethod = @"POST"; + [URLRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:URLRequest + fromData:[log dataUsingEncoding:NSUTF8StringEncoding] + completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error) { + RCTLogError(@"%@", error.localizedDescription); + } + }]; + [task resume]; + }]; } @end diff --git a/React/Base/RCTProfile.h b/React/Base/RCTProfile.h index f722b0a02..2718871d2 100644 --- a/React/Base/RCTProfile.h +++ b/React/Base/RCTProfile.h @@ -25,6 +25,8 @@ NSString *const RCTProfileDidEndProfiling; #if RCT_DEV +@class RCTBridge; + #define RCTProfileBeginFlowEvent() \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wshadow\"") \ @@ -45,14 +47,14 @@ RCT_EXTERN BOOL RCTProfileIsProfiling(void); /** * Start collecting profiling information */ -RCT_EXTERN void RCTProfileInit(void); +RCT_EXTERN void RCTProfileInit(RCTBridge *); /** * Stop profiling and return a JSON string of the collected data - The data * returned is compliant with google's trace event format - the format used * as input to trace-viewer */ -RCT_EXTERN NSString *RCTProfileEnd(void); +RCT_EXTERN NSString *RCTProfileEnd(RCTBridge *); /** * Collects the initial event information for the event and returns a reference ID diff --git a/React/Base/RCTProfile.m b/React/Base/RCTProfile.m index 09989d1cb..1269d7594 100644 --- a/React/Base/RCTProfile.m +++ b/React/Base/RCTProfile.m @@ -10,10 +10,13 @@ #import "RCTProfile.h" #import +#import +#import #import #import "RCTAssert.h" +#import "RCTBridge.h" #import "RCTDefines.h" #import "RCTUtils.h" @@ -32,6 +35,7 @@ NSDictionary *RCTProfileGetMemoryUsage(void); NSString const *RCTProfileTraceEvents = @"traceEvents"; NSString const *RCTProfileSamples = @"samples"; +NSString *const RCTProfilePrefix = @"rct_profile_"; #pragma mark - Variables @@ -92,6 +96,111 @@ NSDictionary *RCTProfileGetMemoryUsage(void) } } +#pragma mark - Module hooks + +@interface RCTBridge (Private) + +- (void)dispatchBlock:(dispatch_block_t)block forModule:(id)module; + +@end + +static const char *RCTProfileProxyClassName(Class); +static const char *RCTProfileProxyClassName(Class class) +{ + return [RCTProfilePrefix stringByAppendingString:NSStringFromClass(class)].UTF8String; +} + +static SEL RCTProfileProxySelector(SEL); +static SEL RCTProfileProxySelector(SEL selector) +{ + NSString *selectorName = NSStringFromSelector(selector); + return NSSelectorFromString([RCTProfilePrefix stringByAppendingString:selectorName]); +} + +static void RCTProfileForwardInvocation(NSObject *, SEL, NSInvocation *); +static void RCTProfileForwardInvocation(NSObject *self, SEL cmd, NSInvocation *invocation) +{ + NSString *name = [NSString stringWithFormat:@"-[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(invocation.selector)]; + SEL newSel = RCTProfileProxySelector(invocation.selector); + + if ([object_getClass(self) instancesRespondToSelector:newSel]) { + invocation.selector = newSel; + RCTProfileBeginEvent(); + [invocation invoke]; + RCTProfileEndEvent(name, @"objc_call,modules,auto", nil); + } else { + // Use original selector to don't change error message + [self doesNotRecognizeSelector:invocation.selector]; + } +} + +static IMP RCTProfileMsgForward(NSObject *, SEL); +static IMP RCTProfileMsgForward(NSObject *self, SEL selector) +{ + IMP imp = _objc_msgForward; +#if !defined(__arm64__) + NSMethodSignature *signature = [self methodSignatureForSelector:selector]; + if (signature.methodReturnType[0] == _C_STRUCT_B && signature.methodReturnLength > 8) { + imp = _objc_msgForward_stret; + } +#endif + return imp; +} + +static void RCTProfileHookModules(RCTBridge *); +static void RCTProfileHookModules(RCTBridge *bridge) +{ + [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) { + [bridge dispatchBlock:^{ + Class moduleClass = object_getClass(module); + Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); + + unsigned int methodCount; + Method *methods = class_copyMethodList(moduleClass, &methodCount); + for (NSUInteger i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector]) { + continue; + } + IMP originalIMP = method_getImplementation(method); + const char *returnType = method_getTypeEncoding(method); + class_addMethod(proxyClass, selector, RCTProfileMsgForward(module, selector), returnType); + class_addMethod(proxyClass, RCTProfileProxySelector(selector), originalIMP, returnType); + } + free(methods); + + for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { + Method oldImp = class_getInstanceMethod(cls, @selector(class)); + class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); + } + + IMP originalFwd = class_replaceMethod(moduleClass, @selector(forwardInvocation:), (IMP)RCTProfileForwardInvocation, "v@:@"); + if (originalFwd != NULL) { + class_addMethod(proxyClass, RCTProfileProxySelector(@selector(forwardInvocation:)), originalFwd, "v@:@"); + } + + objc_registerClassPair(proxyClass); + object_setClass(module, proxyClass); + } forModule:module]; + }]; +} + +void RCTProfileUnhookModules(RCTBridge *); +void RCTProfileUnhookModules(RCTBridge *bridge) +{ + [bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *className, id module, BOOL *stop) { + [bridge dispatchBlock:^{ + Class proxyClass = object_getClass(module); + if (module.class != proxyClass) { + object_setClass(module, module.class); + objc_disposeClassPair(proxyClass); + } + } forModule:module]; + }]; +} + + #pragma mark - Public Functions BOOL RCTProfileIsProfiling(void) @@ -102,8 +211,10 @@ BOOL RCTProfileIsProfiling(void) return profiling; } -void RCTProfileInit(void) +void RCTProfileInit(RCTBridge *bridge) { + RCTProfileHookModules(bridge); + static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _RCTProfileLock = [[NSLock alloc] init]; @@ -121,7 +232,7 @@ void RCTProfileInit(void) object:nil]; } -NSString *RCTProfileEnd(void) +NSString *RCTProfileEnd(RCTBridge *bridge) { [[NSNotificationCenter defaultCenter] postNotificationName:RCTProfileDidEndProfiling object:nil]; @@ -132,6 +243,9 @@ NSString *RCTProfileEnd(void) RCTProfileInfo = nil; RCTProfileOngoingEvents = nil; ); + + RCTProfileUnhookModules(bridge); + return log; } From 1a564eed772b6d0da9dd57456defe625fb73e229 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 4 Jun 2015 09:47:19 -0100 Subject: [PATCH 22/25] [ReactNative] Run UIExplorer tests on sandcastle --- Examples/UIExplorer/UIExplorer/AppDelegate.m | 4 ++++ Examples/UIExplorer/UIExplorer/Info.plist | 6 +++--- Libraries/Image/RCTImageDownloader.m | 4 ++-- Libraries/RCTTest/RCTTestRunner.m | 4 ++++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Examples/UIExplorer/UIExplorer/AppDelegate.m b/Examples/UIExplorer/UIExplorer/AppDelegate.m index d72262e78..9d3adb2ee 100644 --- a/Examples/UIExplorer/UIExplorer/AppDelegate.m +++ b/Examples/UIExplorer/UIExplorer/AppDelegate.m @@ -50,6 +50,10 @@ // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#if RUNNING_ON_CI + jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"UIExplorerApp" launchOptions:launchOptions]; diff --git a/Examples/UIExplorer/UIExplorer/Info.plist b/Examples/UIExplorer/UIExplorer/Info.plist index 245054621..349bd9a28 100644 --- a/Examples/UIExplorer/UIExplorer/Info.plist +++ b/Examples/UIExplorer/UIExplorer/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.$(PRODUCT_NAME:rfc1034identifier) + com.facebook.internal.uiexplorer.local CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +22,8 @@ 1 LSRequiresIPhoneOS + NSLocationWhenInUseUsageDescription + You need to add NSLocationWhenInUseUsageDescription key in Info.plist to enable geolocation, otherwise it is going to *fail silently*! UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -34,8 +36,6 @@ 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/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index aa524ef56..7ff8c6379 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -82,8 +82,8 @@ static NSString *RCTCacheKeyForURL(NSURL *url) RCTImageDownloader *strongSelf = weakSelf; NSArray *blocks = strongSelf->_pendingBlocks[cacheKey]; [strongSelf->_pendingBlocks removeObjectForKey:cacheKey]; - for (RCTCachedDataDownloadBlock block in blocks) { - block(cached, data, error); + for (RCTCachedDataDownloadBlock cacheDownloadBlock in blocks) { + cacheDownloadBlock(cached, data, error); } }); }; diff --git a/Libraries/RCTTest/RCTTestRunner.m b/Libraries/RCTTest/RCTTestRunner.m index 9c0cacf70..8a7e739bb 100644 --- a/Libraries/RCTTest/RCTTestRunner.m +++ b/Libraries/RCTTest/RCTTestRunner.m @@ -35,7 +35,11 @@ sanitizedAppName = [sanitizedAppName stringByReplacingOccurrencesOfString:@"\\" withString:@"-"]; _testController = [[FBSnapshotTestController alloc] initWithTestName:sanitizedAppName]; _testController.referenceImagesDirectory = referenceDir; +#if RUNNING_ON_CI + _scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"]; +#else _scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@.includeRequire.runModule.bundle?dev=true", app]]; +#endif } return self; } From f90fa53df91e90d027f9106b19b0e14c8a677626 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 4 Jun 2015 16:57:33 -0700 Subject: [PATCH 23/25] Revert [react-packager] Add support for nested node_modules --- .../DependencyResolver/ModuleDescriptor.js | 41 +- .../__tests__/ModuleDescriptor-test.js | 109 - .../__tests__/DependencyGraph-test.js | 2157 ++--------------- .../haste/DependencyGraph/index.js | 422 ++-- .../__tests__/HasteDependencyResolver-test.js | 6 +- .../src/DependencyResolver/haste/index.js | 5 +- 6 files changed, 403 insertions(+), 2337 deletions(-) delete mode 100644 packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js index 3c1a1d26d..90db1c4ad 100644 --- a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js +++ b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js @@ -8,9 +8,6 @@ */ 'use strict'; -var Promise = require('bluebird'); -var isAbsolutePath = require('absolute-path'); - function ModuleDescriptor(fields) { if (!fields.id) { throw new Error('Missing required fields id'); @@ -20,13 +17,17 @@ function ModuleDescriptor(fields) { if (!fields.path) { throw new Error('Missing required fields path'); } - if (!isAbsolutePath(fields.path)) { - throw new Error('Expected absolute path but found: ' + fields.path); - } this.path = fields.path; + if (!fields.dependencies) { + throw new Error('Missing required fields dependencies'); + } this.dependencies = fields.dependencies; + this.resolveDependency = fields.resolveDependency; + + this.entry = fields.entry || false; + this.isPolyfill = fields.isPolyfill || false; this.isAsset_DEPRECATED = fields.isAsset_DEPRECATED || false; @@ -49,30 +50,12 @@ function ModuleDescriptor(fields) { this._fields = fields; } -ModuleDescriptor.prototype.loadDependencies = function(loader) { - if (!this.dependencies) { - if (this._loadingDependencies) { - return this._loadingDependencies; - } - - var self = this; - this._loadingDependencies = loader(this).then(function(dependencies) { - self.dependencies = dependencies; - }); - return this._loadingDependencies; - } - - return Promise.resolve(this.dependencies); -}; - ModuleDescriptor.prototype.toJSON = function() { - var ret = {}; - Object.keys(this).forEach(function(prop) { - if (prop[0] !== '_' && typeof this[prop] !== 'function') { - ret[prop] = this[prop]; - } - }, this); - return ret; + return { + id: this.id, + path: this.path, + dependencies: this.dependencies + }; }; module.exports = ModuleDescriptor; diff --git a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js b/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js deleted file mode 100644 index 99bed5df7..000000000 --- a/packager/react-packager/src/DependencyResolver/__tests__/ModuleDescriptor-test.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -jest - .dontMock('absolute-path') - .dontMock('../ModuleDescriptor'); - - -describe('ModuleDescriptor', function() { - var ModuleDescriptor; - var Promise; - - beforeEach(function() { - ModuleDescriptor = require('../ModuleDescriptor'); - Promise = require('bluebird'); - }); - - describe('constructor', function() { - it('should validate fields', function() { - /* eslint no-new:0*/ - expect(function() { - new ModuleDescriptor({}); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: 'foo', - }); - }).toThrow(); - - expect(function() { - new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - }); - }).toThrow(); - - var m = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - expect(m.toJSON()).toEqual({ - altId:undefined, - dependencies: undefined, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - }); - }); - - describe('loadDependencies', function() { - pit('should load dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - - pit('should load cached dependencies', function() { - var mod = new ModuleDescriptor({ - id: 'foo', - path: '/foo', - isAsset: true, - resolution: 1, - }); - - return mod.loadDependencies(function() { - return Promise.resolve([1, 2]); - }).then(function() { - return mod.loadDependencies(function() { - throw new Error('no!'); - }); - }).then(function() { - expect(mod.dependencies).toEqual([1, 2]); - }); - }); - }); -}); 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 935c4e308..c247e59d3 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 @@ -10,12 +10,11 @@ jest .dontMock('../index') - .dontMock('crypto') .dontMock('absolute-path') .dontMock('../docblock') .dontMock('../../replacePatterns') .dontMock('../../../../lib/getAssetDataFromName') - .dontMock('../../../ModuleDescriptor'); + .setMock('../../../ModuleDescriptor', function(data) {return data;}); jest.mock('fs'); @@ -35,14 +34,6 @@ describe('DependencyGraph', function() { }; }); - // There are a lot of crap in ModuleDescriptors, this maps an array - // to get the relevant data. - function getDataFromModules(modules) { - return modules.map(function(module) { - return module.toJSON(); - }); - } - describe('getOrderedDependencies', function() { pit('should get dependencies', function() { var root = '/root'; @@ -67,33 +58,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -126,33 +95,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: []}, ]); }); }); @@ -168,11 +115,9 @@ describe('DependencyGraph', function() { '/**', ' * @providesModule index', ' */', - 'require("./a.json")', - 'require("./b")' + 'require("./a.json")' ].join('\n'), 'a.json': JSON.stringify({}), - 'b.json': JSON.stringify({}), } }); @@ -181,42 +126,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'package/index', path: '/root/index.js', - dependencies: ['./a.json', './b'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./a.json'] }, { id: 'package/a.json', isJSON: true, path: '/root/a.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'package/b.json', - isJSON: true, - path: '/root/b.json', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -244,31 +167,15 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!a', - path: '/root/imgs/a.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['image!a']}, + { id: 'image!a', + path: '/root/imgs/a.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, }, ]); }); @@ -298,31 +205,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png'] }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -357,8 +253,8 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', @@ -368,13 +264,7 @@ describe('DependencyGraph', function() { './imgs/a.png', './imgs/b.png', './imgs/c.png', - ], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + ] }, { id: 'rootPackage/imgs/a.png', @@ -382,32 +272,20 @@ describe('DependencyGraph', function() { resolution: 1.5, dependencies: [], isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'rootPackage/imgs/b.png', path: '/root/imgs/b@.7x.png', resolution: 0.7, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, { id: 'rootPackage/imgs/c.png', path: '/root/imgs/c.png', resolution: 1, dependencies: [], - isAsset: true, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + isAsset: true }, ]); }); @@ -439,20 +317,14 @@ describe('DependencyGraph', function() { assetExts: ['png', 'jpg'], assetRoots_DEPRECATED: ['/root/imgs'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'rootPackage/index', path: '/root/index.js', - dependencies: ['./imgs/a.png', 'image!a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./imgs/a.png', 'image!a'] }, { id: 'rootPackage/imgs/a.png', @@ -460,10 +332,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset: true, resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, { id: 'image!a', @@ -471,10 +339,6 @@ describe('DependencyGraph', function() { dependencies: [], isAsset_DEPRECATED: true, resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, }, ]); }); @@ -504,33 +368,11 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - altId: '/root/a.js', - path: '/root/a.js', - dependencies: ['index'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['a']}, + {id: 'a', altId: '/root/a.js', path: '/root/a.js', dependencies: ['index']}, ]); }); }); @@ -560,105 +402,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with packages with a dot in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - 'require("x.y.z")', - ].join('\n'), - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js' - }), - 'main.js': 'lol' - }, - 'x.y.z': { - 'package.json': JSON.stringify({ - name: 'x.y.z', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js', 'x.y.z'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'x.y.z/main', - path: '/root/x.y.z/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -683,30 +433,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -735,31 +468,14 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'EpicModule', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'EpicModule', altId: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -787,30 +503,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/lib/index', + {id: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, + { id: 'aPackage/lib/index', path: '/root/aPackage/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -835,82 +534,13 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'test/lib/index', + {id: 'test/index', path: '/root/index.js', dependencies: ['./lib/']}, + { id: 'test/lib/index', path: '/root/lib/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should resolve require to main if it is a dir w/ a package.json', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'package.json': JSON.stringify({ - 'main': 'main.js', - }), - 'index.js': 'lol', - 'main.js': 'lol', - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'test/index', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: '/root/lib/main.js', - path: '/root/lib/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -938,21 +568,10 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + {id: 'index', altId: '/root/index.js', path: '/root/index.js', dependencies: ['aPackage']}, ]); }); }); @@ -993,32 +612,18 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js')) .toEqual([ - { - id: 'index', + { id: 'index', altId: '/root/somedir/somefile.js', path: '/root/somedir/somefile.js', - dependencies: ['c'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['c'] }, - { - id: 'c', + { id: 'c', altId: '/root/c.js', path: '/root/c.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1054,31 +659,17 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage', + { id: 'aPackage', altId: '/root/b.js', path: '/root/b.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1102,19 +693,12 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['lolomg'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['lolomg'] } ]); }); @@ -1148,29 +732,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1205,30 +776,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage/subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', + { id: 'aPackage/subdir/lolynot', path: '/symlinkedPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1263,56 +820,24 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/main', - altId: undefined, + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./subdir/lolynot'] }, - { - id: 'aPackage/subdir/lolynot', - altId: undefined, + { id: 'aPackage/subdir/lolynot', path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['../other'] }, - { - id: 'aPackage/other', - altId: undefined, + { id: 'aPackage/other', path: '/root/aPackage/other.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1345,32 +870,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, - { - id: 'aPackage/client', - altId: undefined, + { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1403,30 +912,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', - altId: undefined, path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1461,29 +956,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1518,29 +1000,16 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1585,58 +1054,28 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/client', path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./node', './dir/server.js'] }, { id: 'aPackage/not-node', path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./not-browser'] }, { id: 'aPackage/browser', path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, { id: 'aPackage/dir/client', path: '/root/aPackage/dir/client.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -1681,633 +1120,20 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, { id: 'aPackage/index', path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['node-package'] }, { id: 'browser-package/index', path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - describe('node_modules', function() { - pit('should work with nested node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with specific paths', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/lol', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('nested node_modules with browser field', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - browser: { - './lol': './wow' - } - }), - 'main.js': 'bar 1 module', - 'lol.js': '', - 'wow.js': '', - }, - } - }, - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - browser: './main2', - }), - 'main2.js': 'bar 2 module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/wow', - path: '/root/node_modules/foo/node_modules/bar/wow.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main2', - path: '/root/node_modules/bar/main2.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('node_modules should support multi level', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("bar");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '', - }, - }, - 'path': { - 'to': { - 'bar.js': [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'), - }, - 'node_modules': {}, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - path: '/root/path/to/bar.js', - altId: '/root/path/to/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - ].join('\n'), - 'node_modules': { - 'react-tools': { - 'package.json': JSON.stringify({ - name: 'react-tools', - main: 'main.js', - }), - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - 'submodule': { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - } - }, - 'ember': { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - 'main.js':[ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['shouldWork', 'dontWork', 'wontWork'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-tools/main.js', - altId:'react-tools/main', - dependencies: ['submodule'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'submodule/main', - path: '/root/node_modules/react-tools/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should ignore modules it cant find (assumes own require system)', function() { - // For example SourceMap.js implements it's own require system. - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/lol");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo/lol'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - pit('should work with node packages with a .js in the name', function() { - var root = '/root'; - fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - ].join('\n'), - 'node_modules': { - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js' - }), - 'main.js': 'lol' - } - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['sha.js'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'sha.js/main', - path: '/root/node_modules/sha.js/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2361,36 +1187,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2427,36 +1239,22 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['index.js'] = filesystem.root['index.js'].replace('require("foo")', ''); triggerFileChange('change', 'index.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['aPackage'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: [] + }, + ]); }); }); }); @@ -2493,31 +1291,19 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { delete filesystem.root.foo; triggerFileChange('delete', 'foo.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + path: '/root/index.js', + dependencies: ['aPackage', 'foo'] + }, { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, ]); }); @@ -2556,7 +1342,7 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2568,54 +1354,27 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar', - altId: '/root/bar.js', - path: '/root/bar.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - altId: '/root/foo.js', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, + dependencies: ['aPackage', 'foo'] + }, + { id: 'aPackage/main', + path: '/root/aPackage/main.js', + dependencies: ['bar'] + }, + { id: 'bar', + altId: '/root/bar.js', + path: '/root/bar.js', + dependencies: ['foo'] + }, + { id: 'foo', + altId: '/root/foo.js', + path: '/root/foo.js', + dependencies: ['aPackage'] + }, ]); }); }); @@ -2641,50 +1400,32 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['image!foo'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['image!foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'image!foo', - path: '/root/foo.png', - dependencies: [], - isAsset_DEPRECATED: true, - resolution: 1, - isAsset: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset_DEPRECATED: true, + resolution: 1, + }, + ]); }); }); }); @@ -2711,49 +1452,30 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ { id: 'index', altId: 'aPackage/index', path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['./foo.png'] } ]); filesystem.root['foo.png'] = ''; triggerFileChange('add', 'foo.png', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', - altId: 'aPackage/index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolveDependency: undefined, + { id: 'index', altId: 'aPackage/index', + path: '/root/index.js', + dependencies: ['./foo.png'] + }, + { id: 'aPackage/foo.png', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + resolution: 1, }, ]); }); @@ -2798,7 +1520,7 @@ describe('DependencyGraph', function() { return false; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { filesystem.root['bar.js'] = [ '/**', ' * @providesModule bar', @@ -2810,42 +1532,21 @@ describe('DependencyGraph', function() { filesystem.root.aPackage['main.js'] = 'require("bar")'; triggerFileChange('change', 'aPackage/main.js', root); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['bar'] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); @@ -2883,407 +1584,25 @@ describe('DependencyGraph', function() { fileWatcher: fileWatcher, assetExts: ['png', 'jpg'], }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { + return dgraph.load().then(function() { triggerFileChange('change', 'aPackage', '/root', { isDirectory: function(){ return true; } }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) .toEqual([ - { - id: 'index', altId: '/root/index.js', + { id: 'index', altId: '/root/index.js', path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage', 'foo'] }, - { - id: 'aPackage/main', + { id: 'aPackage/main', path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: [] }, - { - id: 'foo', + { id: 'foo', altId: '/root/foo.js', path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('updates package.json', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage'); - triggerFileChange('change', 'index.js', root); - - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['bPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bPackage/main', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('changes to browser field', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - 'browser.js': 'browser', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/browser', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('removes old package from cache', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - 'aPackage': { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js' - }), - 'main.js': 'main', - 'browser.js': 'browser', - } - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - triggerFileChange('change', 'package.json', '/root/aPackage'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\nfoo module', - 'node_modules': { - 'bar': { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'bar 1 module', - }, - } - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - expect(getDataFromModules(deps)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main', - altId: undefined, - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root.node_modules.foo['main.js'] = 'lol'; - triggerFileChange('change', 'main.js', '/root/node_modules/foo'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main', - altId: undefined, - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - }); - - pit('should update node package main changes', function() { - var root = '/root'; - var filesystem = fs.__setMockFilesystem({ - 'root': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - 'node_modules': { - 'foo': { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'foo module', - 'browser.js': 'foo module', - }, - }, - } - }); - - var dgraph = new DependencyGraph({ - roots: [root], - fileWatcher: fileWatcher, - assetExts: ['png', 'jpg'], - }); - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) { - filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ - name: 'foo', - main: 'main.js', - browser: 'browser.js', - }); - triggerFileChange('change', 'package.json', '/root/node_modules/foo'); - - return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) { - expect(getDataFromModules(deps2)) - .toEqual([ - { - id: 'index', - altId: '/root/index.js', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/browser', - altId: undefined, - path: '/root/node_modules/foo/browser.js', - dependencies: [], - isAsset: false, - isAsset_DEPRECATED: false, - isJSON: undefined, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, + dependencies: ['aPackage'] }, ]); }); diff --git a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index 1aa8b1290..0881e5dc7 100644 --- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -19,7 +19,6 @@ var debug = require('debug')('DependecyGraph'); var util = require('util'); var declareOpts = require('../../../lib/declareOpts'); var getAssetDataFromName = require('../../../lib/getAssetDataFromName'); -var crypto = require('crypto'); var readFile = Promise.promisify(fs.readFile); var readDir = Promise.promisify(fs.readdir); @@ -46,11 +45,7 @@ var validateOpts = declareOpts({ assetExts: { type: 'array', required: true, - }, - _providesModuleNodeModules: { - type: 'array', - default: ['react-tools', 'react-native'], - }, + } }); function DependecyGraph(options) { @@ -74,8 +69,6 @@ function DependecyGraph(options) { '\.(' + ['js', 'json'].concat(this._assetExts).join('|') + ')$' ); - this._providesModuleNodeModules = opts._providesModuleNodeModules; - // Kick off the search process to precompute the dependency graph. this._init(); } @@ -97,99 +90,58 @@ DependecyGraph.prototype.load = function() { * Given an entry file return an array of all the dependent module descriptors. */ DependecyGraph.prototype.getOrderedDependencies = function(entryPath) { - return this.load().then(function() { - var absolutePath = this._getAbsolutePath(entryPath); - if (absolutePath == null) { - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - entryPath, - this._roots - ); - } - - var module = this._graph[absolutePath]; - if (module == null) { - throw new Error('Module with path "' + entryPath + '" is not in graph'); - } - - var self = this; - var deps = []; - var visited = Object.create(null); - - // Node haste sucks. Id's aren't unique. So to make sure our entry point - // is the thing that ends up in our dependency list. - var graphMap = Object.create(this._moduleById); - graphMap[module.id] = module; - - // Recursively collect the dependency list. - function collect(mod) { - deps.push(mod); - - if (mod.dependencies == null) { - return mod.loadDependencies(function() { - return readFile(mod.path, 'utf8').then(function(content) { - return extractRequires(content); - }); - }).then(function() { - return iter(mod); - }); - } - - return iter(mod); - } - - function iter(mod) { - var p = Promise.resolve(); - mod.dependencies.forEach(function(name) { - var dep = self.resolveDependency(mod, name); - - if (dep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`.', - name, - mod.id - ); - return; - } - - p = p.then(function() { - if (!visited[realId(dep)]) { - visited[realId(dep)] = true; - return collect(dep); - } - return null; - }); - }); - return p; - } - - visited[realId(module)] = true; - return collect(module).then(function() { - return deps; - }); - }.bind(this)); -}; - -function browserFieldRedirect(packageJson, modulePath, isMain) { - if (packageJson.browser && typeof packageJson.browser === 'object') { - if (isMain) { - var tmpMain = packageJson.browser[modulePath] || - packageJson.browser[sansExtJs(modulePath)] || - packageJson.browser[withExtJs(modulePath)]; - if (tmpMain) { - return tmpMain; - } - } else { - var relPath = './' + path.relative(packageJson._root, modulePath); - var tmpModulePath = packageJson.browser[withExtJs(relPath)] || - packageJson.browser[sansExtJs(relPath)]; - if (tmpModulePath) { - return path.join(packageJson._root, tmpModulePath); - } - } + var absolutePath = this._getAbsolutePath(entryPath); + if (absolutePath == null) { + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } - return modulePath; -} + + var module = this._graph[absolutePath]; + if (module == null) { + throw new Error('Module with path "' + entryPath + '" is not in graph'); + } + + var self = this; + var deps = []; + var visited = Object.create(null); + + // Node haste sucks. Id's aren't unique. So to make sure our entry point + // is the thing that ends up in our dependency list. + var graphMap = Object.create(this._moduleById); + graphMap[module.id] = module; + + // Recursively collect the dependency list. + function collect(module) { + deps.push(module); + + module.dependencies.forEach(function(name) { + var id = sansExtJs(name); + var dep = self.resolveDependency(module, id); + + if (dep == null) { + debug( + 'WARNING: Cannot find required module `%s` from module `%s`.', + name, + module.id + ); + return; + } + + if (!visited[dep.id]) { + visited[dep.id] = true; + collect(dep); + } + }); + } + + visited[module.id] = true; + collect(module); + + return deps; +}; /** * Given a module descriptor `fromModule` return the module descriptor for @@ -205,7 +157,7 @@ DependecyGraph.prototype.resolveDependency = function( // Process DEPRECATED global asset requires. if (assetMatch && assetMatch[1]) { if (!this._assetMap_DEPRECATED[assetMatch[1]]) { - debug('WARNING: Cannot find asset:', assetMatch[1]); + debug('WARINING: Cannot find asset:', assetMatch[1]); return null; } return this._assetMap_DEPRECATED[assetMatch[1]]; @@ -225,39 +177,19 @@ DependecyGraph.prototype.resolveDependency = function( depModuleId = fromPackageJson.browser[depModuleId]; } - - var packageName = depModuleId.replace(/\/.+/, ''); - packageJson = this._lookupNodePackage(fromModule.path, packageName); - - if (packageJson != null && packageName !== depModuleId) { - modulePath = path.join( - packageJson._root, - path.relative(packageName, depModuleId) - ); - - modulePath = browserFieldRedirect(packageJson, modulePath); - - dep = this._graph[withExtJs(modulePath)]; - if (dep != null) { - return dep; - } - } - // `depModuleId` is simply a top-level `providesModule`. // `depModuleId` is a package module but given the full path from the // package, i.e. package_name/module_name - if (packageJson == null && this._moduleById[sansExtJs(depModuleId)]) { + if (this._moduleById[sansExtJs(depModuleId)]) { return this._moduleById[sansExtJs(depModuleId)]; } - if (packageJson == null) { - // `depModuleId` is a package and it's depending on the "main" resolution. - packageJson = this._packagesById[depModuleId]; - } + // `depModuleId` is a package and it's depending on the "main" resolution. + packageJson = this._packagesById[depModuleId]; - // We are being forgiving here and not raising an error because we could be + // We are being forgiving here and raising an error because we could be // processing a file that uses it's own require system. - if (packageJson == null || packageName !== depModuleId) { + if (packageJson == null) { debug( 'WARNING: Cannot find required module `%s` from module `%s`.', depModuleId, @@ -266,8 +198,33 @@ DependecyGraph.prototype.resolveDependency = function( return null; } - // We are requiring node or a haste package via it's main file. - dep = this._resolvePackageMain(packageJson); + var main; + + // We prioritize the `browser` field if it's a module path. + if (typeof packageJson.browser === 'string') { + main = packageJson.browser; + } else { + main = packageJson.main || 'index'; + } + + // If there is a mapping for main in the `browser` field. + if (packageJson.browser && typeof packageJson.browser === 'object') { + var tmpMain = packageJson.browser[main] || + packageJson.browser[withExtJs(main)] || + packageJson.browser[sansExtJs(main)]; + if (tmpMain) { + main = tmpMain; + } + } + + modulePath = withExtJs(path.join(packageJson._root, main)); + dep = this._graph[modulePath]; + + // Some packages use just a dir and rely on an index.js inside that dir. + if (dep == null) { + dep = this._graph[path.join(packageJson._root, main, 'index.js')]; + } + if (dep == null) { throw new Error( 'Cannot find package main file for package: ' + packageJson._root @@ -291,22 +248,28 @@ DependecyGraph.prototype.resolveDependency = function( // modulePath: /x/y/a/b var dir = path.dirname(fromModule.path); modulePath = path.join(dir, depModuleId); - modulePath = browserFieldRedirect(packageJson, modulePath); - dep = this._graph[modulePath] || - this._graph[modulePath + '.js'] || - this._graph[modulePath + '.json']; - - // Maybe the dependency is a directory and there is a packageJson and/or index.js inside it. - if (dep == null) { - var dirPackageJson = this._packageByRoot[path.join(dir, depModuleId).replace(/\/$/, '')]; - if (dirPackageJson) { - dep = this._resolvePackageMain(dirPackageJson); - } else { - dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + if (packageJson.browser && typeof packageJson.browser === 'object') { + var relPath = './' + path.relative(packageJson._root, modulePath); + var tmpModulePath = packageJson.browser[withExtJs(relPath)] || + packageJson.browser[sansExtJs(relPath)]; + if (tmpModulePath) { + modulePath = path.join(packageJson._root, tmpModulePath); } } + // JS modules can be required without extensios. + if (!this._isFileAsset(modulePath) && !modulePath.match(/\.json$/)) { + modulePath = withExtJs(modulePath); + } + + dep = this._graph[modulePath]; + + // Maybe the dependency is a directory and there is an index.js inside it. + if (dep == null) { + dep = this._graph[path.join(dir, depModuleId, 'index.js')]; + } + // Maybe it's an asset with @n.nx resolution and the path doesn't map // to the id if (dep == null && this._isFileAsset(modulePath)) { @@ -328,25 +291,6 @@ DependecyGraph.prototype.resolveDependency = function( } }; -DependecyGraph.prototype._resolvePackageMain = function(packageJson) { - var main; - // We prioritize the `browser` field if it's a module path. - if (typeof packageJson.browser === 'string') { - main = packageJson.browser; - } else { - main = packageJson.main || 'index'; - } - - // If there is a mapping for main in the `browser` field. - main = browserFieldRedirect(packageJson, main, true); - - var modulePath = withExtJs(path.join(packageJson._root, main)); - - return this._graph[modulePath] || - // Some packages use just a dir and rely on an index.js inside that dir. - this._graph[path.join(packageJson._root, main, 'index.js')]; -}; - /** * Intiates the filewatcher and kicks off the search process. */ @@ -395,9 +339,9 @@ DependecyGraph.prototype._search = function() { }); var processing = self._findAndProcessPackage(files, dir) - .then(function() { - return Promise.all(modulePaths.map(self._processModule.bind(self))); - }); + .then(function() { + return Promise.all(modulePaths.map(self._processModule.bind(self))); + }); return Promise.all([ processing, @@ -414,8 +358,10 @@ DependecyGraph.prototype._search = function() { * and update indices. */ DependecyGraph.prototype._findAndProcessPackage = function(files, root) { + var self = this; + var packagePath; - for (var i = 0; i < files.length; i++) { + for (var i = 0; i < files.length ; i++) { var file = files[i]; if (path.basename(file) === 'package.json') { packagePath = file; @@ -443,6 +389,14 @@ DependecyGraph.prototype._processPackage = function(packagePath) { return Promise.resolve(); } + if (packageJson.name == null) { + debug( + 'WARNING: package.json `%s` is missing a name field', + packagePath + ); + return Promise.resolve(); + } + packageJson._root = packageRoot; self._addPackageToIndices(packageJson); @@ -452,16 +406,12 @@ DependecyGraph.prototype._processPackage = function(packagePath) { DependecyGraph.prototype._addPackageToIndices = function(packageJson) { this._packageByRoot[packageJson._root] = packageJson; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - this._packagesById[packageJson.name] = packageJson; - } + this._packagesById[packageJson.name] = packageJson; }; DependecyGraph.prototype._removePackageFromIndices = function(packageJson) { delete this._packageByRoot[packageJson._root]; - if (!this._isInNodeModules(packageJson._root) && packageJson.name != null) { - delete this._packagesById[packageJson.name]; - } + delete this._packagesById[packageJson.name]; }; /** @@ -491,14 +441,6 @@ DependecyGraph.prototype._processModule = function(modulePath) { return Promise.resolve(module); } - if (this._isInNodeModules(modulePath)) { - moduleData.id = this._lookupName(modulePath); - moduleData.dependencies = null; - module = new ModuleDescriptor(moduleData); - this._updateGraphWithModule(module); - return Promise.resolve(module); - } - var self = this; return readFile(modulePath, 'utf8') .then(function(content) { @@ -527,7 +469,7 @@ DependecyGraph.prototype._processModule = function(modulePath) { */ DependecyGraph.prototype._lookupName = function(modulePath) { var packageJson = this._lookupPackage(modulePath); - if (packageJson == null || packageJson.name == null) { + if (packageJson == null) { return path.resolve(modulePath); } else { var relativePath = @@ -542,10 +484,6 @@ DependecyGraph.prototype._deleteModule = function(module) { // Others may keep a reference so we mark it as deleted. module.deleted = true; - if (this._isInNodeModules(module.path)) { - return; - } - // Haste allows different module to have the same id. if (this._moduleById[module.id] === module) { delete this._moduleById[module.id]; @@ -566,10 +504,6 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { this._graph[module.path] = module; - if (this._isInNodeModules(module.path)) { - return; - } - if (this._moduleById[module.id]) { debug( 'WARNING: Top-level module name conflict `%s`.\n' + @@ -593,14 +527,28 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) { * Find the nearest package to a module. */ DependecyGraph.prototype._lookupPackage = function(modulePath) { - return lookupPackage(path.dirname(modulePath), this._packageByRoot); -}; + var packageByRoot = this._packageByRoot; -/** - * Find the nearest node package to a module. - */ -DependecyGraph.prototype._lookupNodePackage = function(startPath, packageName) { - return lookupNodePackage(path.dirname(startPath), this._packageByRoot, packageName); + /** + * Auxiliary function to recursively lookup a package. + */ + function lookupPackage(currDir) { + // ideally we stop once we're outside root and this can be a simple child + // dir check. However, we have to support modules that was symlinked inside + // our project root. + if (currDir === '/') { + return null; + } else { + var packageJson = packageByRoot[currDir]; + if (packageJson) { + return packageJson; + } else { + return lookupPackage(path.dirname(currDir)); + } + } + } + + return lookupPackage(path.dirname(modulePath)); }; /** @@ -625,14 +573,12 @@ DependecyGraph.prototype._processFileChange = function( } var isPackage = path.basename(filePath) === 'package.json'; - var packageJson; - if (isPackage) { - packageJson = this._packageByRoot[path.dirname(absPath)]; - } - if (eventType === 'delete') { - if (isPackage && packageJson) { - this._removePackageFromIndices(packageJson); + if (isPackage) { + var packageJson = this._packageByRoot[path.dirname(absPath)]; + if (packageJson) { + this._removePackageFromIndices(packageJson); + } } else { var module = this._graph[absPath]; if (module == null) { @@ -645,14 +591,7 @@ DependecyGraph.prototype._processFileChange = function( var self = this; this._loading = this._loading.then(function() { if (isPackage) { - self._removePackageFromIndices(packageJson); - return self._processPackage(absPath) - .then(function(p) { - return self._resolvePackageMain(p); - }) - .then(function(mainModule) { - return self._processModule(mainModule.path); - }); + return self._processPackage(absPath); } return self._processModule(absPath); }); @@ -736,25 +675,6 @@ DependecyGraph.prototype._isFileAsset = function(file) { return this._assetExts.indexOf(extname(file)) !== -1; }; -DependecyGraph.prototype._isInNodeModules = function(file) { - var inNodeModules = file.indexOf('/node_modules/') !== -1; - - if (!inNodeModules) { - return false; - } - - var dirs = this._providesModuleNodeModules; - - for (var i = 0; i < dirs.length; i++) { - var index = file.indexOf(dirs[i]); - if (index !== -1) { - return file.slice(index).indexOf('/node_modules/') !== -1; - } - } - - return true; -}; - /** * Extract all required modules from a `code` string. */ @@ -864,54 +784,6 @@ function extname(name) { return path.extname(name).replace(/^\./, ''); } -function realId(module) { - if (module._realId) { - return module._realId; - } - - var hash = crypto.createHash('md5'); - hash.update(module.id); - hash.update(module.path); - Object.defineProperty(module, '_realId', { value: hash.digest('hex') }); - return module._realId; -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupPackage(currDir, packageByRoot) { - // ideally we stop once we're outside root and this can be a simple child - // dir check. However, we have to support modules that was symlinked inside - // our project root. - if (currDir === '/') { - return null; - } else { - var packageJson = packageByRoot[currDir]; - if (packageJson) { - return packageJson; - } else { - return lookupPackage(path.dirname(currDir), packageByRoot); - } - } -} - -/** - * Auxiliary function to recursively lookup a package. - */ -function lookupNodePackage(currDir, packageByRoot, packageName) { - if (currDir === '/') { - return null; - } - var packageRoot = path.join(currDir, 'node_modules', packageName); - - var packageJson = packageByRoot[packageRoot]; - if (packageJson) { - return packageJson; - } else { - return lookupNodePackage(path.dirname(currDir), packageByRoot, packageName); - } -} - function NotFoundError() { Error.call(this); Error.captureStackTrace(this, this.constructor); diff --git a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js index 1f8c95479..9bc8b8b95 100644 --- a/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js +++ b/packager/react-packager/src/DependencyResolver/haste/__tests__/HasteDependencyResolver-test.js @@ -40,7 +40,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -123,7 +123,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); @@ -207,7 +207,7 @@ describe('HasteDependencyResolver', function() { // Is there a better way? How can I mock the prototype instead? var depGraph = depResolver._depGraph; depGraph.getOrderedDependencies.mockImpl(function() { - return Promise.resolve(deps); + return deps; }); depGraph.load.mockImpl(function() { return Promise.resolve(); diff --git a/packager/react-packager/src/DependencyResolver/haste/index.js b/packager/react-packager/src/DependencyResolver/haste/index.js index d7a8c0eb1..da68785ea 100644 --- a/packager/react-packager/src/DependencyResolver/haste/index.js +++ b/packager/react-packager/src/DependencyResolver/haste/index.js @@ -91,8 +91,9 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) { var depGraph = this._depGraph; var self = this; - return depGraph.getOrderedDependencies(main) - .then(function(dependencies) { + return depGraph.load() + .then(function() { + var dependencies = depGraph.getOrderedDependencies(main); var mainModuleId = dependencies[0].id; self._prependPolyfillDependencies(dependencies, opts.dev); From 45d8fb0ef64a8a41929d61fe822a64c2febe5455 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Fri, 5 Jun 2015 08:46:17 -0700 Subject: [PATCH 24/25] Removed deprecated RCT_EXPORT + code paths --- Examples/Movies/SearchScreen.js | 2 +- Examples/UIExplorer/StatusBarIOSExample.js | 22 +- Examples/UIExplorer/UIExplorerList.js | 2 +- .../DatePicker/DatePickerIOS.ios.js | 6 +- .../ProgressViewIOS/ProgressViewIOS.ios.js | 8 +- Libraries/Components/ScrollView/ScrollView.js | 20 +- .../SegmentedControlIOS.ios.js | 8 +- .../Components/StatusBar/StatusBarIOS.ios.js | 28 +- Libraries/Components/TextInput/TextInput.js | 79 ++---- Libraries/Image/Image.ios.js | 15 +- Libraries/Image/RCTNetworkImageViewManager.m | 2 +- Libraries/Image/RCTStaticImageManager.m | 2 +- Libraries/Picker/PickerIOS.ios.js | 4 +- React/Base/RCTBridge.m | 249 +++++++----------- React/Base/RCTBridgeModule.h | 9 - React/Base/RCTConvert.h | 3 +- React/Base/RCTConvert.m | 16 +- React/Base/RCTRedBox.m | 5 - React/Modules/RCTStatusBarManager.h | 8 + React/Modules/RCTStatusBarManager.m | 33 +-- React/Modules/RCTUIManager.m | 72 ----- React/Views/RCTDatePickerManager.h | 7 + React/Views/RCTDatePickerManager.m | 23 +- React/Views/RCTPickerManager.m | 6 +- React/Views/RCTScrollViewManager.h | 7 + React/Views/RCTScrollViewManager.m | 23 +- React/Views/RCTTextFieldManager.m | 3 +- 27 files changed, 233 insertions(+), 429 deletions(-) diff --git a/Examples/Movies/SearchScreen.js b/Examples/Movies/SearchScreen.js index 05ab80187..e7eb7f2b4 100644 --- a/Examples/Movies/SearchScreen.js +++ b/Examples/Movies/SearchScreen.js @@ -281,7 +281,7 @@ var SearchScreen = React.createClass({ renderRow={this.renderRow} onEndReached={this.onEndReached} automaticallyAdjustContentInsets={false} - keyboardDismissMode="onDrag" + keyboardDismissMode="on-drag" keyboardShouldPersistTaps={true} showsVerticalScrollIndicator={false} />; diff --git a/Examples/UIExplorer/StatusBarIOSExample.js b/Examples/UIExplorer/StatusBarIOSExample.js index 545136c49..ab649197e 100644 --- a/Examples/UIExplorer/StatusBarIOSExample.js +++ b/Examples/UIExplorer/StatusBarIOSExample.js @@ -32,11 +32,11 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Style).map((key) => + {['default', 'light-content'].map((style) => StatusBarIOS.setStyle(StatusBarIOS.Style[key])}> + onPress={() => StatusBarIOS.setStyle(style)}> - setStyle(StatusBarIOS.Style.{key}) + setStyle('{style}') )} @@ -48,11 +48,11 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Style).map((key) => + {['default', 'light-content'].map((style) => StatusBarIOS.setStyle(StatusBarIOS.Style[key], true)}> + onPress={() => StatusBarIOS.setStyle(style, true)}> - setStyle(StatusBarIOS.Style.{key}, true) + setStyle('{style}', true) )} @@ -64,18 +64,18 @@ exports.examples = [{ render() { return ( - {Object.keys(StatusBarIOS.Animation).map((key) => + {['none', 'fade', 'slide'].map((animation) => StatusBarIOS.setHidden(true, StatusBarIOS.Animation[key])}> + onPress={() => StatusBarIOS.setHidden(true, animation)}> - setHidden(true, StatusBarIOS.Animation.{key}) + setHidden(true, '{animation}') StatusBarIOS.setHidden(false, StatusBarIOS.Animation[key])}> + onPress={() => StatusBarIOS.setHidden(false, animation)}> - setHidden(false, StatusBarIOS.Animation.{key}) + setHidden(false, '{animation}') diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index df9a3b123..cd73e6d06 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -157,7 +157,7 @@ class UIExplorerList extends React.Component { renderSectionHeader={this._renderSectionHeader} keyboardShouldPersistTaps={true} automaticallyAdjustContentInsets={false} - keyboardDismissMode="onDrag" + keyboardDismissMode="on-drag" /> ); diff --git a/Libraries/Components/DatePicker/DatePickerIOS.ios.js b/Libraries/Components/DatePicker/DatePickerIOS.ios.js index 41fc9b877..f184c6f79 100644 --- a/Libraries/Components/DatePicker/DatePickerIOS.ios.js +++ b/Libraries/Components/DatePicker/DatePickerIOS.ios.js @@ -120,7 +120,7 @@ var DatePickerIOS = React.createClass({ ), onTouchStart: this.scrollResponderHandleTouchStart, onTouchMove: this.scrollResponderHandleTouchMove, @@ -308,7 +299,7 @@ var ScrollView = React.createClass({ onResponderRelease: this.scrollResponderHandleResponderRelease, onResponderReject: this.scrollResponderHandleResponderReject, }; - + var ScrollViewClass; if (Platform.OS === 'ios') { ScrollViewClass = RCTScrollView; @@ -318,6 +309,13 @@ var ScrollView = React.createClass({ } else { ScrollViewClass = AndroidScrollView; } + var keyboardDismissModeConstants = { + 'none': RCTScrollViewConsts.KeyboardDismissMode.None, // default + 'interactive': RCTScrollViewConsts.KeyboardDismissMode.Interactive, + 'on-drag': RCTScrollViewConsts.KeyboardDismissMode.OnDrag, + }; + props.keyboardDismissMode = props.keyboardDismissMode ? + keyboardDismissModeConstants[props.keyboardDismissMode] : undefined; } invariant( ScrollViewClass !== undefined, diff --git a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js index 23d952776..ec3b6c614 100644 --- a/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js +++ b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.ios.js @@ -108,13 +108,7 @@ var styles = StyleSheet.create({ var RCTSegmentedControl = requireNativeComponent( 'RCTSegmentedControl', - null + SegmentedControlIOS ); -if (__DEV__) { - verifyPropTypes( - RCTSegmentedControl, - RCTSegmentedControl.viewConfig - ); -} module.exports = SegmentedControlIOS; diff --git a/Libraries/Components/StatusBar/StatusBarIOS.ios.js b/Libraries/Components/StatusBar/StatusBarIOS.ios.js index 14a5cecf2..adfed78b7 100644 --- a/Libraries/Components/StatusBar/StatusBarIOS.ios.js +++ b/Libraries/Components/StatusBar/StatusBarIOS.ios.js @@ -13,26 +13,26 @@ var RCTStatusBarManager = require('NativeModules').StatusBarManager; +type StatusBarStyle = $Enum<{ + 'default': string, + 'light-content': string, +}>; + +type StatusBarAnimation = $Enum<{ + 'none': string, + 'fade': string, + 'slide': string, +}>; + var StatusBarIOS = { - Style: { - default: RCTStatusBarManager.Style.default, - lightContent: RCTStatusBarManager.Style.lightContent - }, - - Animation: { - none: RCTStatusBarManager.Animation.none, - fade: RCTStatusBarManager.Animation.fade, - slide: RCTStatusBarManager.Animation.slide, - }, - - setStyle(style: number, animated?: boolean) { + setStyle(style: StatusBarStyle, animated?: boolean) { animated = animated || false; RCTStatusBarManager.setStyle(style, animated); }, - setHidden(hidden: boolean, animation: number) { - animation = animation || StatusBarIOS.Animation.none; + setHidden(hidden: boolean, animation?: StatusBarAnimation) { + animation = animation || 'none'; RCTStatusBarManager.setHidden(hidden, animation); }, }; diff --git a/Libraries/Components/TextInput/TextInput.js b/Libraries/Components/TextInput/TextInput.js index 03f374b67..7d3f04b33 100644 --- a/Libraries/Components/TextInput/TextInput.js +++ b/Libraries/Components/TextInput/TextInput.js @@ -31,10 +31,6 @@ var emptyFunction = require('emptyFunction'); var invariant = require('invariant'); var merge = require('merge'); -var autoCapitalizeConsts = RCTUIManager.UIText.AutocapitalizationType; -var keyboardTypeConsts = RCTUIManager.UIKeyboardType; -var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType; - var RCTTextViewAttributes = merge(ReactNativeViewAttributes.UIView, { autoCorrect: true, autoCapitalize: true, @@ -96,10 +92,6 @@ var viewConfigAndroid = { validAttributes: AndroidTextInputAttributes, }; -var crossPlatformKeyboardTypeMap = { - 'numeric': 'decimal-pad', -}; - type DefaultProps = { bufferDelay: number; }; @@ -171,8 +163,11 @@ var TextInput = React.createClass({ * Determines which keyboard to open, e.g.`numeric`. */ keyboardType: PropTypes.oneOf([ - 'default', - // iOS + // Cross-platform + 'default', + 'numeric', + 'email-address', + // iOS-only 'ascii-capable', 'numbers-and-punctuation', 'url', @@ -182,9 +177,6 @@ var TextInput = React.createClass({ 'decimal-pad', 'twitter', 'web-search', - // Cross-platform - 'numeric', - 'email-address', ]), /** * Determines how the return key should look. @@ -426,18 +418,12 @@ var TextInput = React.createClass({ _renderIOS: function() { var textContainer; - var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; - var clearButtonMode = RCTUIManager.UITextField.clearButtonMode[this.props.clearButtonMode]; + var props = this.props; + props.style = [styles.input, this.props.style]; - var keyboardType = keyboardTypeConsts[ - crossPlatformKeyboardTypeMap[this.props.keyboardType] || - this.props.keyboardType - ]; - var returnKeyType = returnKeyTypeConsts[this.props.returnKeyType]; - - if (!this.props.multiline) { + if (!props.multiline) { for (var propKey in onlyMultiline) { - if (this.props[propKey]) { + if (props[propKey]) { throw new Error( 'TextInput prop `' + propKey + '` is only supported with multiline.' ); @@ -446,77 +432,48 @@ var TextInput = React.createClass({ textContainer = true} - onLayout={this.props.onLayout} - placeholder={this.props.placeholder} - placeholderTextColor={this.props.placeholderTextColor} text={this.state.bufferedValue} - autoCapitalize={autoCapitalize} - autoCorrect={this.props.autoCorrect} - clearButtonMode={clearButtonMode} - clearTextOnFocus={this.props.clearTextOnFocus} - selectTextOnFocus={this.props.selectTextOnFocus} />; } else { for (var propKey in notMultiline) { - if (this.props[propKey]) { + if (props[propKey]) { throw new Error( 'TextInput prop `' + propKey + '` cannot be used with multiline.' ); } } - var children = this.props.children; + var children = props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); invariant( - !(this.props.value && childCount), + !(props.value && childCount), 'Cannot specify both value and children.' ); if (childCount > 1) { children = {children}; } - if (this.props.inputView) { - children = [children, this.props.inputView]; + if (props.inputView) { + children = [children, props.inputView]; } textContainer = ; } @@ -524,14 +481,14 @@ var TextInput = React.createClass({ + testID={props.testID}> {textContainer} ); }, _renderAndroid: function() { - var autoCapitalize = autoCapitalizeConsts[this.props.autoCapitalize]; + var autoCapitalize = RCTUIManager.UIText.AutocapitalizationType[this.props.autoCapitalize]; var children = this.props.children; var childCount = 0; ReactChildren.forEach(children, () => ++childCount); diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 9b84937af..23e2c7a63 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -146,20 +146,11 @@ var Image = React.createClass({ if (this.props.style && this.props.style.tintColor) { warning(RawImage === RCTStaticImage, 'tintColor style only supported on static images.'); } - var resizeMode = this.props.resizeMode || style.resizeMode; - var contentModes = NativeModules.UIManager.UIView.ContentMode; - var contentMode; - if (resizeMode === ImageResizeMode.stretch) { - contentMode = contentModes.ScaleToFill; - } else if (resizeMode === ImageResizeMode.contain) { - contentMode = contentModes.ScaleAspectFit; - } else { // ImageResizeMode.cover or undefined - contentMode = contentModes.ScaleAspectFill; - } + var resizeMode = this.props.resizeMode || style.resizeMode || 'cover'; var nativeProps = merge(this.props, { style, - contentMode, + resizeMode, tintColor: style.tintColor, }); if (isStored) { @@ -187,7 +178,7 @@ var nativeOnlyProps = { src: true, defaultImageSrc: true, imageTag: true, - contentMode: true, + resizeMode: true, }; if (__DEV__) { verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps); diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 2ecf69971..005b726cf 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -29,6 +29,6 @@ RCT_EXPORT_MODULE() RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL) -RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) +RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) @end diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m index ce6aab187..bdc6f0596 100644 --- a/Libraries/Image/RCTStaticImageManager.m +++ b/Libraries/Image/RCTStaticImageManager.m @@ -26,7 +26,7 @@ RCT_EXPORT_MODULE() } RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets) -RCT_EXPORT_VIEW_PROPERTY(contentMode, UIViewContentMode) +RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage) { if (json) { diff --git a/Libraries/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js index b1b1d06f4..42302fa26 100644 --- a/Libraries/Picker/PickerIOS.ios.js +++ b/Libraries/Picker/PickerIOS.ios.js @@ -59,7 +59,7 @@ var PickerIOS = React.createClass({ ", NSStringFromClass(self.class), self, _methodName, _JSMethodName]; + return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@();>", + [self class], self, [self methodName], _JSMethodName]; } @end @@ -562,19 +498,10 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void) const char **entries = (const char **)(mach_header + addr); // Create method - RCTModuleMethod *moduleMethod; - if (entries[2] == NULL) { - - // Legacy support for RCT_EXPORT() - moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) - objCMethodName:@(entries[0]) - JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil]; - } else { - moduleMethod = [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) - objCMethodName:strlen(entries[1]) ? @(entries[1]) : nil - JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil]; - } - + RCTModuleMethod *moduleMethod = + [[RCTModuleMethod alloc] initWithReactMethodName:@(entries[0]) + objCMethodName:@(entries[1]) + JSMethodName:strlen(entries[2]) ? @(entries[2]) : nil]; // Cache method NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName]; methodsByModuleClassName[moduleMethod.moduleClassName] = @@ -726,7 +653,7 @@ static NSDictionary *RCTLocalModulesConfig() @"methodID": @(methods.count), @"type": @"local" }; - [RCTLocalMethodNames addObject:methodName]; + [RCTLocalMethodNames addObject:methodName]; } // Add module and method lookup @@ -1610,7 +1537,7 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin - (void)stopProfiling { - RCTAssertMainThread(); + RCTAssertMainThread(); [_javaScriptExecutor executeBlockOnJavaScriptQueue:^{ NSString *log = RCTProfileEnd(self); diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 34b861ff3..f3a9a5a3e 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -145,15 +145,6 @@ extern const dispatch_queue_t RCTJSThread; static const char *__rct_export_entry__[] = { __func__, #method, #js_name }; \ } -/** - * Deprecated, do not use. - */ -#define RCT_EXPORT(js_name) \ - _Pragma("message(\"RCT_EXPORT is deprecated. Use RCT_EXPORT_METHOD instead.\")") \ - __attribute__((used, section("__DATA,RCTExport"))) \ - __attribute__((__aligned__(1))) \ - static const char *__rct_export_entry__[] = { __func__, #js_name, NULL } - /** * The queue that will be used to call all exported methods. If omitted, this * will call on the default background queue, which is avoids blocking the main diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index ee43c1159..145e88b21 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -23,6 +23,8 @@ */ @interface RCTConvert : NSObject ++ (id)id:(id)json; + + (BOOL)BOOL:(id)json; + (double)double:(id)json; + (float)float:(id)json; @@ -52,7 +54,6 @@ + (NSWritingDirection)NSWritingDirection:(id)json; + (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json; + (UITextFieldViewMode)UITextFieldViewMode:(id)json; -+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; + (UIKeyboardType)UIKeyboardType:(id)json; + (UIReturnKeyType)UIReturnKeyType:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 3c9143c17..3bdf59753 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -21,6 +21,8 @@ void RCTLogConvertError(id json, const char *type) json, [json classForCoder], type); } +RCT_CONVERTER(id, id, self) + RCT_CONVERTER(BOOL, BOOL, boolValue) RCT_NUMBER_CONVERTER(double, doubleValue) RCT_NUMBER_CONVERTER(float, floatValue) @@ -219,12 +221,6 @@ RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{ @"always": @(UITextFieldViewModeAlways), }), UITextFieldViewModeNever, integerValue) -RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ - @"none": @(UIScrollViewKeyboardDismissModeNone), - @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag), - @"interactive": @(UIScrollViewKeyboardDismissModeInteractive), -}), UIScrollViewKeyboardDismissModeNone, integerValue) - RCT_ENUM_CONVERTER(UIKeyboardType, (@{ @"default": @(UIKeyboardTypeDefault), @"ascii-capable": @(UIKeyboardTypeASCIICapable), @@ -237,6 +233,8 @@ RCT_ENUM_CONVERTER(UIKeyboardType, (@{ @"decimal-pad": @(UIKeyboardTypeDecimalPad), @"twitter": @(UIKeyboardTypeTwitter), @"web-search": @(UIKeyboardTypeWebSearch), + // Added for Android compatibility + @"numeric": @(UIKeyboardTypeDecimalPad), }), UIKeyboardTypeDefault, integerValue) RCT_ENUM_CONVERTER(UIReturnKeyType, (@{ @@ -267,7 +265,11 @@ RCT_ENUM_CONVERTER(UIViewContentMode, (@{ @"top-right": @(UIViewContentModeTopRight), @"bottom-left": @(UIViewContentModeBottomLeft), @"bottom-right": @(UIViewContentModeBottomRight), -}), UIViewContentModeScaleToFill, integerValue) + // Cross-platform values + @"cover": @(UIViewContentModeScaleAspectFill), + @"contain": @(UIViewContentModeScaleAspectFit), + @"stretch": @(UIViewContentModeScaleToFill), +}), UIViewContentModeScaleAspectFill, integerValue) RCT_ENUM_CONVERTER(UIBarStyle, (@{ @"default": @(UIBarStyleDefault), diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index 2d35dcd6f..6330454ab 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -85,11 +85,6 @@ selector:@selector(dismiss) name:RCTReloadNotification object:nil]; - - [notificationCenter addObserver:self - selector:@selector(dismiss) - name:RCTJavaScriptDidLoadNotification - object:nil]; } return self; } diff --git a/React/Modules/RCTStatusBarManager.h b/React/Modules/RCTStatusBarManager.h index 40feee5c0..aee9b8642 100644 --- a/React/Modules/RCTStatusBarManager.h +++ b/React/Modules/RCTStatusBarManager.h @@ -10,6 +10,14 @@ #import #import "RCTBridgeModule.h" +#import "RCTConvert.h" + +@interface RCTConvert (UIStatusBar) + ++ (UIStatusBarStyle)UIStatusBarStyle:(id)json; ++ (UIStatusBarAnimation)UIStatusBarAnimation:(id)json; + +@end @interface RCTStatusBarManager : NSObject diff --git a/React/Modules/RCTStatusBarManager.m b/React/Modules/RCTStatusBarManager.m index 04bb39038..cb9ddfe69 100644 --- a/React/Modules/RCTStatusBarManager.m +++ b/React/Modules/RCTStatusBarManager.m @@ -11,6 +11,21 @@ #import "RCTLog.h" +@implementation RCTConvert (UIStatusBar) + +RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{ + @"default": @(UIStatusBarStyleDefault), + @"light-content": @(UIStatusBarStyleLightContent), +}), UIStatusBarStyleDefault, integerValue); + +RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{ + @"none": @(UIStatusBarAnimationNone), + @"fade": @(UIStatusBarAnimationFade), + @"slide": @(UIStatusBarAnimationSlide), +}), UIStatusBarAnimationNone, integerValue); + +@end + @implementation RCTStatusBarManager static BOOL RCTViewControllerBasedStatusBarAppearance() @@ -18,7 +33,8 @@ static BOOL RCTViewControllerBasedStatusBarAppearance() static BOOL value; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue]; + value = [[[NSBundle mainBundle] objectForInfoDictionaryKey: + @"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue]; }); return value; @@ -55,19 +71,4 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden } } -- (NSDictionary *)constantsToExport -{ - return @{ - @"Style": @{ - @"default": @(UIStatusBarStyleDefault), - @"lightContent": @(UIStatusBarStyleLightContent), - }, - @"Animation": @{ - @"none": @(UIStatusBarAnimationNone), - @"fade": @(UIStatusBarAnimationFade), - @"slide": @(UIStatusBarAnimationSlide), - }, - }; -} - @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index cc580e903..570bdfef7 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -1397,11 +1397,6 @@ RCT_EXPORT_METHOD(clearJSResponder) NSMutableDictionary *allJSConstants = [@{ @"customBubblingEventTypes": [self customBubblingEventTypes], @"customDirectEventTypes": [self customDirectEventTypes], - @"NSTextAlignment": @{ - @"Left": @(NSTextAlignmentLeft), - @"Center": @(NSTextAlignmentCenter), - @"Right": @(NSTextAlignmentRight), - }, @"Dimensions": @{ @"window": @{ @"width": @(RCTScreenSize().width), @@ -1413,73 +1408,6 @@ RCT_EXPORT_METHOD(clearJSResponder) @"height": @(RCTScreenSize().height), }, }, - @"StyleConstants": @{ - @"PointerEventsValues": @{ - @"none": @(RCTPointerEventsNone), - @"box-none": @(RCTPointerEventsBoxNone), - @"box-only": @(RCTPointerEventsBoxOnly), - @"auto": @(RCTPointerEventsUnspecified), - }, - }, - @"UIText": @{ - @"AutocapitalizationType": @{ - @"characters": @(UITextAutocapitalizationTypeAllCharacters), - @"sentences": @(UITextAutocapitalizationTypeSentences), - @"words": @(UITextAutocapitalizationTypeWords), - @"none": @(UITextAutocapitalizationTypeNone), - }, - }, - @"UITextField": @{ - @"clearButtonMode": @{ - @"never": @(UITextFieldViewModeNever), - @"while-editing": @(UITextFieldViewModeWhileEditing), - @"unless-editing": @(UITextFieldViewModeUnlessEditing), - @"always": @(UITextFieldViewModeAlways), - }, - }, - @"UIKeyboardType": @{ - @"default": @(UIKeyboardTypeDefault), - @"ascii-capable": @(UIKeyboardTypeASCIICapable), - @"numbers-and-punctuation": @(UIKeyboardTypeNumbersAndPunctuation), - @"url": @(UIKeyboardTypeURL), - @"number-pad": @(UIKeyboardTypeNumberPad), - @"phone-pad": @(UIKeyboardTypePhonePad), - @"name-phone-pad": @(UIKeyboardTypeNamePhonePad), - @"decimal-pad": @(UIKeyboardTypeDecimalPad), - @"email-address": @(UIKeyboardTypeEmailAddress), - @"twitter": @(UIKeyboardTypeTwitter), - @"web-search": @(UIKeyboardTypeWebSearch), - }, - @"UIReturnKeyType": @{ - @"default": @(UIReturnKeyDefault), - @"go": @(UIReturnKeyGo), - @"google": @(UIReturnKeyGoogle), - @"join": @(UIReturnKeyJoin), - @"next": @(UIReturnKeyNext), - @"route": @(UIReturnKeyRoute), - @"search": @(UIReturnKeySearch), - @"send": @(UIReturnKeySend), - @"yahoo": @(UIReturnKeyYahoo), - @"done": @(UIReturnKeyDone), - @"emergency-call": @(UIReturnKeyEmergencyCall), - }, - @"UIView": @{ - @"ContentMode": @{ - @"ScaleToFill": @(UIViewContentModeScaleToFill), - @"ScaleAspectFit": @(UIViewContentModeScaleAspectFit), - @"ScaleAspectFill": @(UIViewContentModeScaleAspectFill), - @"Redraw": @(UIViewContentModeRedraw), - @"Center": @(UIViewContentModeCenter), - @"Top": @(UIViewContentModeTop), - @"Bottom": @(UIViewContentModeBottom), - @"Left": @(UIViewContentModeLeft), - @"Right": @(UIViewContentModeRight), - @"TopLeft": @(UIViewContentModeTopLeft), - @"TopRight": @(UIViewContentModeTopRight), - @"BottomLeft": @(UIViewContentModeBottomLeft), - @"BottomRight": @(UIViewContentModeBottomRight), - }, - }, } mutableCopy]; [_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, RCTViewManager *manager, BOOL *stop) { diff --git a/React/Views/RCTDatePickerManager.h b/React/Views/RCTDatePickerManager.h index eb424085d..73d88b6dd 100644 --- a/React/Views/RCTDatePickerManager.h +++ b/React/Views/RCTDatePickerManager.h @@ -8,6 +8,13 @@ */ #import "RCTViewManager.h" +#import "RCTConvert.h" + +@interface RCTConvert(UIDatePicker) + ++ (UIDatePickerMode)UIDatePickerMode:(id)json; + +@end @interface RCTDatePickerManager : RCTViewManager diff --git a/React/Views/RCTDatePickerManager.m b/React/Views/RCTDatePickerManager.m index 36397d6e5..d8bbde703 100644 --- a/React/Views/RCTDatePickerManager.m +++ b/React/Views/RCTDatePickerManager.m @@ -10,7 +10,6 @@ #import "RCTDatePickerManager.h" #import "RCTBridge.h" -#import "RCTConvert.h" #import "RCTEventDispatcher.h" #import "UIView+React.h" @@ -20,7 +19,7 @@ RCT_ENUM_CONVERTER(UIDatePickerMode, (@{ @"time": @(UIDatePickerModeTime), @"date": @(UIDatePickerModeDate), @"datetime": @(UIDatePickerModeDateAndTime), - //@"countdown": @(UIDatePickerModeCountDownTimer) // not supported yet + @"countdown": @(UIDatePickerModeCountDownTimer), // not supported yet }), UIDatePickerModeTime, integerValue) @end @@ -31,9 +30,12 @@ RCT_EXPORT_MODULE() - (UIView *)view { + // TODO: we crash here if the RCTDatePickerManager is released + // while the UIDatePicker is still sending onChange events. To + // fix this we should maybe subclass UIDatePicker and make it + // be its own event target. UIDatePicker *picker = [[UIDatePicker alloc] init]; - [picker addTarget:self - action:@selector(onChange:) + [picker addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; return picker; } @@ -56,17 +58,10 @@ RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone) - (NSDictionary *)constantsToExport { - UIDatePicker *dp = [[UIDatePicker alloc] init]; - [dp layoutIfNeeded]; - + UIDatePicker *view = [[UIDatePicker alloc] init]; return @{ - @"ComponentHeight": @(CGRectGetHeight(dp.frame)), - @"ComponentWidth": @(CGRectGetWidth(dp.frame)), - @"DatePickerModes": @{ - @"time": @(UIDatePickerModeTime), - @"date": @(UIDatePickerModeDate), - @"datetime": @(UIDatePickerModeDateAndTime), - } + @"ComponentHeight": @(view.intrinsicContentSize.height), + @"ComponentWidth": @(view.intrinsicContentSize.width), }; } diff --git a/React/Views/RCTPickerManager.m b/React/Views/RCTPickerManager.m index 3bbc60b94..de6c1f916 100644 --- a/React/Views/RCTPickerManager.m +++ b/React/Views/RCTPickerManager.m @@ -27,10 +27,10 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger) - (NSDictionary *)constantsToExport { - RCTPicker *pv = [[RCTPicker alloc] init]; + RCTPicker *view = [[RCTPicker alloc] init]; return @{ - @"ComponentHeight": @(CGRectGetHeight(pv.frame)), - @"ComponentWidth": @(CGRectGetWidth(pv.frame)) + @"ComponentHeight": @(view.intrinsicContentSize.height), + @"ComponentWidth": @(view.intrinsicContentSize.width) }; } diff --git a/React/Views/RCTScrollViewManager.h b/React/Views/RCTScrollViewManager.h index 9fec3422d..83b3126e8 100644 --- a/React/Views/RCTScrollViewManager.h +++ b/React/Views/RCTScrollViewManager.h @@ -8,6 +8,13 @@ */ #import "RCTViewManager.h" +#import "RCTConvert.h" + +@interface RCTConvert (UIScrollView) + ++ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; + +@end @interface RCTScrollViewManager : RCTViewManager diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 8441de74d..15803df1e 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -10,11 +10,22 @@ #import "RCTScrollViewManager.h" #import "RCTBridge.h" -#import "RCTConvert.h" #import "RCTScrollView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" +@implementation RCTConvert (UIScrollView) + +RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ + @"none": @(UIScrollViewKeyboardDismissModeNone), + @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag), + @"interactive": @(UIScrollViewKeyboardDismissModeInteractive), + // Backwards compatibility + @"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag), +}), UIScrollViewKeyboardDismissModeNone, integerValue) + +@end + @implementation RCTScrollViewManager RCT_EXPORT_MODULE() @@ -53,14 +64,10 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle) - (NSDictionary *)constantsToExport { return @{ + // TODO: unused - remove these? @"DecelerationRate": @{ - @"Normal": @(UIScrollViewDecelerationRateNormal), - @"Fast": @(UIScrollViewDecelerationRateFast), - }, - @"KeyboardDismissMode": @{ - @"None": @(UIScrollViewKeyboardDismissModeNone), - @"Interactive": @(UIScrollViewKeyboardDismissModeInteractive), - @"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag), + @"normal": @(UIScrollViewDecelerationRateNormal), + @"fast": @(UIScrollViewDecelerationRateFast), }, }; } diff --git a/React/Views/RCTTextFieldManager.m b/React/Views/RCTTextFieldManager.m index ff401a719..7b867bd0d 100644 --- a/React/Views/RCTTextFieldManager.m +++ b/React/Views/RCTTextFieldManager.m @@ -25,7 +25,7 @@ RCT_EXPORT_MODULE() RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL) RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL) -RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL) +RCT_REMAP_VIEW_PROPERTY(editable, enabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(text, NSString) @@ -36,6 +36,7 @@ RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType) RCT_EXPORT_VIEW_PROPERTY(returnKeyType, UIReturnKeyType) RCT_EXPORT_VIEW_PROPERTY(enablesReturnKeyAutomatically, BOOL) RCT_EXPORT_VIEW_PROPERTY(secureTextEntry, BOOL) +RCT_REMAP_VIEW_PROPERTY(password, secureTextEntry, BOOL) // backwards compatibility RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor) RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType) RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField) From bb95400f244015c96fe201d22d40f65bd6805775 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Fri, 5 Jun 2015 10:54:17 -0700 Subject: [PATCH 25/25] [ReactNative] Fix POPAnimation export issue from bad transform Summary: Some transform is going wrong here and produces broken code @public Test Plan: Test on OSS repo, e2e tests pass --- Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js index 0245cc144..c8e289431 100644 --- a/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js +++ b/Libraries/BatchedBridge/BatchedBridgedModules/POPAnimation.js @@ -7,7 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule POPAnimation - * @flow */ 'use strict'; @@ -17,7 +16,7 @@ if (!RCTPOPAnimationManager) { // workaround to enable its availability to be determined at runtime. // For Flow let's pretend like we always export POPAnimation // so all our users don't need to do null checks - module.exports = ((null: any): typeof POPAnimation); + module.exports = null; } else { var ReactPropTypes = require('ReactPropTypes');