diff --git a/Examples/UIExplorer/ProgressViewIOSExample.js b/Examples/UIExplorer/ProgressViewIOSExample.js
new file mode 100644
index 00000000000000..f0a17a7c6e78f2
--- /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/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index 45a679b315a9a5..acbba362921ea0 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/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js
index a030220cabb1af..df9a3b12322f30 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/IntegrationTests/AsyncStorageTest.js b/IntegrationTests/AsyncStorageTest.js
index 6d13bb6e9fe22a..911887d3e2dd89 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/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.android.js
new file mode 100644
index 00000000000000..abda1c368cab31
--- /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 ProgressViewIOS
+ */
+
+'use strict';
+
+var React = require('React');
+var StyleSheet = require('StyleSheet');
+var Text = require('Text');
+var View = require('View');
+
+var DummyProgressViewIOS = 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 = DummyProgressViewIOS;
diff --git a/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js b/Libraries/Components/ProgressViewIOS/ProgressViewIOS.ios.js
new file mode 100644
index 00000000000000..b4fb3e76875195
--- /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/Components/SegmentedControlIOS/SegmentedControlIOS.android.js b/Libraries/Components/SegmentedControlIOS/SegmentedControlIOS.android.js
index 28fbea0271dc11..848144bff7f039 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;
diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js
index 533652f6504c91..dcbfbeee19d91e 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 d99bf7380af4ae..a0891714f26766 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');
@@ -50,7 +52,7 @@ var onlyChild = require('onlyChild');
*/
var TouchableOpacity = React.createClass({
- mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
+ mixins: [TimerMixin, Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
propTypes: {
...TouchableWithoutFeedback.propTypes,
@@ -72,6 +74,7 @@ var TouchableOpacity = React.createClass({
},
componentDidMount: function() {
+ ensurePositiveDelayProps(this.props);
ensureComponentIsNative(this.refs[CHILD_REF]);
},
@@ -79,6 +82,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
@@ -86,6 +93,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);
@@ -102,20 +110,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();
},
@@ -128,7 +142,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 cd9ea02fdf1517..227cbeae282782 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,23 +33,44 @@ 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: {
/**
* 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,
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,13 +96,22 @@ 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 {
// 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,
diff --git a/Libraries/Components/Touchable/ensurePositiveDelayProps.js b/Libraries/Components/Touchable/ensurePositiveDelayProps.js
new file mode 100644
index 00000000000000..4c6525a54130ca
--- /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/Inspector.js b/Libraries/Inspector.js
index 5c70be2e8686a2..e0017c3cf32888 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/Picker/PickerIOS.ios.js b/Libraries/Picker/PickerIOS.ios.js
index b2c3c3927717a5..b1b1d06f466189 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;
diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js
index 4d03c6641535f7..732d4c0294c9af 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 ef1ba1496e8c62..194bbc5ddeeda4 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 4846c885e302e8..1966c60452835c 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 @@ - (instancetype)init
selector:@selector(handleRemoteNotificationReceived:)
name:RCTRemoteNotificationReceived
object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleRemoteNotificationsRegistered:)
+ name:RCTRemoteNotificationsRegistered
+ object:nil];
}
return self;
}
@@ -52,6 +67,21 @@ + (void)application:(UIApplication *)application didRegisterUserNotificationSett
}
}
++ (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 @@ - (void)handleRemoteNotificationReceived:(NSNotification *)notification
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 @@ - (void)handleRemoteNotificationReceived:(NSNotification *)notification
]);
}
-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];
diff --git a/Libraries/ReactIOS/InspectorOverlay/BorderBox.js b/Libraries/ReactIOS/InspectorOverlay/BorderBox.js
new file mode 100644
index 00000000000000..caee8ccd6b3334
--- /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/BoxInspector.js b/Libraries/ReactIOS/InspectorOverlay/BoxInspector.js
new file mode 100644
index 00000000000000..e50d9869aac7ce
--- /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
new file mode 100644
index 00000000000000..a3851001c2ee45
--- /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(200, 230, 255, 0.8)',
+ },
+ padding: {
+ borderColor: 'rgba(77, 255, 0, 0.3)',
+ },
+ margin: {
+ borderColor: 'rgba(255, 132, 0, 0.3)',
+ },
+});
+
+module.exports = ElementBox;
+
diff --git a/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js
new file mode 100644
index 00000000000000..310374fb148f75
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/ElementProperties.js
@@ -0,0 +1,105 @@
+/**
+ * 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 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 selection = this.props.selection;
+ // Without the `TouchableWithoutFeedback`, taps on this inspector pane
+ // would change the inspected element to whatever is under the inspector
+ return (
+
+
+
+ {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-between',
+ },
+ info: {
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
+ padding: 10,
+ },
+ path: {
+ color: 'white',
+ fontSize: 9,
+ },
+});
+
+module.exports = ElementProperties;
diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
similarity index 53%
rename from Libraries/ReactIOS/InspectorOverlay.js
rename to Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
index eeb6e7965728f9..3b391502ffa4fc 100644
--- a/Libraries/ReactIOS/InspectorOverlay.js
+++ b/Libraries/ReactIOS/InspectorOverlay/InspectorOverlay.js
@@ -17,12 +17,16 @@ var StyleSheet = require('StyleSheet');
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() {
return {
frame: null,
+ pointerY: 0,
hierarchy: [],
+ selection: -1,
};
},
@@ -33,15 +37,34 @@ 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({
hierarchy,
- frame: {left, top, width, height}
+ pointerY: locationY,
+ selection: hierarchy.length - 1,
+ frame: {left, top, width, height},
+ style: publicInstance.props ? publicInstance.props.style : {},
});
}
);
},
+ 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;
@@ -49,18 +72,32 @@ 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 distanceToTop = this.state.pointerY;
+ var distanceToBottom = Dimensions.get('window').height - distanceToTop;
- var justifyContent = distanceToTop > distanceToBottom
+ justifyContent = distanceToTop > distanceToBottom
? 'flex-start'
: 'flex-end';
- content.push();
- 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,
},
- frame: {
- position: 'absolute',
- backgroundColor: 'rgba(155,155,255,0.3)',
- },
- 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 00000000000000..702d01e1da35a8
--- /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/InspectorOverlay/resolveBoxStyle.js b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js
new file mode 100644
index 00000000000000..e0bfb601c129a3
--- /dev/null
+++ b/Libraries/ReactIOS/InspectorOverlay/resolveBoxStyle.js
@@ -0,0 +1,59 @@
+/**
+ * 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;
+ subs.forEach(sub => {
+ res[sub] = style[prefix] || 0;
+ });
+ if (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;
+ set = true;
+ }
+ });
+ if (!set) {
+ return;
+ }
+ return res;
+}
+
+function capFirst(text) {
+ return text[0].toUpperCase() + text.slice(1);
+}
+
+module.exports = resolveBoxStyle;
+
diff --git a/Libraries/Utilities/mapWithSeparator.js b/Libraries/Utilities/mapWithSeparator.js
new file mode 100644
index 00000000000000..4aa8665c3520b1
--- /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;
diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js
index c81183b5b2f3e0..6b1c992ccedaa0 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/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js b/Libraries/vendor/react_contrib/interactions/Touchable/Touchable.js
index 37c42382712627..2a0dd1813aec0b 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) {
diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h
index b260fca4a4c6cc..ed1ff90b8cb098 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 2416e974c1757f..8a5f23f9d2f256 100644
--- a/React/Base/RCTDevMenu.m
+++ b/React/Base/RCTDevMenu.m
@@ -43,6 +43,28 @@ - (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
@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 @@ @implementation RCTDevMenu
NSURLSessionDataTask *_updateTask;
NSURL *_liveReloadURL;
BOOL _jsLoaded;
+ NSArray *_presentedItems;
+ NSMutableArray *_extraMenuItems;
}
@synthesize bridge = _bridge;
@@ -94,6 +118,7 @@ - (instancetype)init
_defaults = [NSUserDefaults standardUserDefaults];
_settings = [[NSMutableDictionary alloc] init];
+ _extraMenuItems = [NSMutableArray array];
// Delay setup until after Bridge init
[self settingsDidChange];
@@ -110,6 +135,13 @@ - (instancetype)init
[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
@@ -225,32 +257,82 @@ - (void)toggle
}
}
-RCT_EXPORT_METHOD(show)
+- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler
{
- if (_actionSheet || !_bridge) {
- return;
+ [_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;
+ }]];
}
- 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";
+ 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;
+ }]];
- UIActionSheet *actionSheet =
- [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
- delegate:self
- cancelButtonTitle:nil
- destructiveButtonTitle:nil
- otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, fpsMonitor, nil];
+ NSString *fpsMonitor = _showFPS ? @"Hide FPS Monitor" : @"Show FPS Monitor";
+ [items addObject:[[RCTDevMenuItem alloc] initWithTitle:fpsMonitor handler:^{
+ self.showFPS = !_showFPS;
+ }]];
- [actionSheet addButtonWithTitle:@"Inspect Element"];
+ [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];
- [actionSheet addButtonWithTitle:liveReloadTitle];
- [actionSheet addButtonWithTitle:profilingTitle];
+ return items;
+}
+
+RCT_EXPORT_METHOD(show)
+{
+ if (_actionSheet || !_bridge) {
+ return;
+ }
+
+ UIActionSheet *actionSheet = [[UIActionSheet alloc] init];
+ actionSheet.title = @"React Native: Development";
+ actionSheet.delegate = self;
+
+ NSArray *items = [self menuItems];
+ for (RCTDevMenuItem *item in items) {
+ [actionSheet addButtonWithTitle:item.title];
}
[actionSheet addButtonWithTitle:@"Cancel"];
@@ -259,13 +341,7 @@ - (void)toggle
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
@@ -275,48 +351,16 @@ - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger
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
@@ -438,6 +482,7 @@ @implementation RCTDevMenu
- (void)show {}
- (void)reload {}
+- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler {}
@end
diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m
index f95f134c34f16b..4ffef7ee752cad 100644
--- a/React/Base/RCTTouchHandler.m
+++ b/React/Base/RCTTouchHandler.m
@@ -36,8 +36,6 @@ @implementation RCTTouchHandler
BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS;
- NSMutableArray *_pendingTouches;
- NSMutableArray *_bridgeInteractionTiming;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@@ -52,9 +50,6 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
_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.
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 5c34d0e0a582db..641500b3884976 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 712e9724e3cd5c..613b13163446d5 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -24,7 +24,7 @@
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 2c01161d48fa93..76f7fa885556e9 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 @@ - (id)_appendItemForKey:(NSString *)key toArray:(NSMutableArray *)result
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 @@ - (id)_writeEntry:(NSArray *)entry
id keyError = [self _appendItemForKey:key toArray:result];
RCTAppendError(keyError, &errors);
}
- [self _writeManifest:&errors];
callback(@[errors ?: [NSNull null], result]);
}
@@ -221,6 +254,38 @@ - (id)_writeEntry:(NSArray *)entry
}
}
+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)
{
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index c7309989b4ca88..3c0cfe72284851 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 00000000000000..ae8a6a38805b3d
--- /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 00000000000000..deb6285a68c6ec
--- /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
diff --git a/packager/README.md b/packager/README.md
index 8f9f649bf5ac17..c3fae48064e801 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
diff --git a/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js b/packager/react-packager/src/DependencyResolver/ModuleDescriptor.js
index 90db1c4ade5f54..3c1a1d26dc54c1 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 00000000000000..99bed5df70eab0
--- /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 c247e59d37a32f..935c4e3084bf86 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
+ },
]);
});
});
@@ -115,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({}),
}
});
@@ -126,20 +181,42 @@ 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', './b'],
+ 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,
+ },
+ {
+ id: 'package/b.json',
+ isJSON: true,
+ path: '/root/b.json',
+ dependencies: [],
+ isAsset: false,
+ isAsset_DEPRECATED: false,
+ isPolyfill: false,
+ resolution: undefined,
+ resolveDependency: undefined,
},
]);
});
@@ -167,15 +244,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 +298,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 +357,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 +368,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 +382,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 +439,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 +460,10 @@ describe('DependencyGraph', function() {
dependencies: [],
isAsset: true,
resolution: 1,
+ isAsset_DEPRECATED: false,
+ isJSON: undefined,
+ isPolyfill: false,
+ resolveDependency: undefined,
},
{
id: 'image!a',
@@ -339,6 +471,10 @@ describe('DependencyGraph', function() {
dependencies: [],
isAsset_DEPRECATED: true,
resolution: 1,
+ isAsset: false,
+ isJSON: undefined,
+ isPolyfill: false,
+ resolveDependency: undefined,
},
]);
});
@@ -368,11 +504,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 +560,105 @@ 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,
+ },
+ ]);
+ });
+ });
+
+ 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,
},
]);
});
@@ -433,13 +683,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 +735,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 +787,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 +835,82 @@ 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,
+ },
+ ]);
+ });
+ });
+
+ 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,
},
]);
});
@@ -568,10 +938,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 +993,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 +1054,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 +1102,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 +1148,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 +1205,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 +1263,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 +1345,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 +1403,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 +1461,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 +1518,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 +1585,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,65 +1681,81 @@ 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('file watch updating', function() {
- var triggerFileChange;
-
- beforeEach(function() {
- fileWatcher = {
- on: function(eventType, callback) {
- if (eventType !== 'all') {
- throw new Error('Can only handle "all" event in watcher.');
- }
- triggerFileChange = callback;
- return this;
- }
- };
- });
-
- pit('updates module dependencies', function() {
+ describe('node_modules', function() {
+ pit('should work with nested node_modules', function() {
var root = '/root';
- var filesystem = fs.__setMockFilesystem({
+ fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
- 'require("aPackage")',
- 'require("foo")'
- ].join('\n'),
- 'foo': [
- '/**',
- ' * @providesModule foo',
- ' */',
- 'require("aPackage")'
+ 'require("foo");',
+ 'require("bar");',
].join('\n'),
- 'aPackage': {
- 'package.json': JSON.stringify({
- name: 'aPackage',
- main: 'main.js'
- }),
- 'main.js': 'main',
- }
+ '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',
+ },
+ },
}
});
@@ -1187,29 +1764,640 @@ describe('DependencyGraph', function() {
fileWatcher: fileWatcher,
assetExts: ['png', 'jpg'],
});
- return dgraph.load().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'))
- .toEqual([
- { id: 'index', altId: '/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: ['foo', 'bar'],
+ isAsset: false,
+ isAsset_DEPRECATED: false,
+ isJSON: undefined,
+ isPolyfill: false,
+ resolution: undefined,
+ resolveDependency: undefined,
},
- { id: 'aPackage/main',
- path: '/root/aPackage/main.js',
- dependencies: []
+ {
+ 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('updates module dependencies on file change', function() {
+ pit('nested node_modules with specific paths', function() {
var root = '/root';
- var filesystem = fs.__setMockFilesystem({
+ 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,
+ },
+ ]);
+ });
+ });
+ });
+
+ describe('file watch updating', function() {
+ var triggerFileChange;
+
+ beforeEach(function() {
+ fileWatcher = {
+ on: function(eventType, callback) {
+ if (eventType !== 'all') {
+ throw new Error('Can only handle "all" event in watcher.');
+ }
+ triggerFileChange = callback;
+ return this;
+ }
+ };
+ });
+
+ pit('updates module dependencies', function() {
+ var root = '/root';
+ var filesystem = fs.__setMockFilesystem({
+ 'root': {
+ 'index.js': [
+ '/**',
+ ' * @providesModule index',
+ ' */',
+ 'require("aPackage")',
+ 'require("foo")'
+ ].join('\n'),
+ 'foo': [
+ '/**',
+ ' * @providesModule foo',
+ ' */',
+ '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('require("foo")', '');
+ triggerFileChange('change', 'index.js', root);
+ 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/main',
+ path: '/root/aPackage/main.js',
+ dependencies: [],
+ isAsset: false,
+ isAsset_DEPRECATED: false,
+ isJSON: undefined,
+ isPolyfill: false,
+ resolution: undefined,
+ resolveDependency: undefined,
+ },
+ ]);
+ });
+ });
+ });
+
+ pit('updates module dependencies on file change', function() {
+ var root = '/root';
+ var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
@@ -1239,22 +2427,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 +2493,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 +2556,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 +2568,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 +2641,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 +2711,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 +2798,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 +2810,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 +2883,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 0881e5dc77dea0..1aa8b12902aceb 100644
--- a/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
+++ b/packager/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
@@ -19,6 +19,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 +46,11 @@ var validateOpts = declareOpts({
assetExts: {
type: 'array',
required: true,
- }
+ },
+ _providesModuleNodeModules: {
+ type: 'array',
+ default: ['react-tools', 'react-native'],
+ },
});
function DependecyGraph(options) {
@@ -69,6 +74,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 +97,100 @@ 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);
-
- // 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;
+ 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);
+ });
}
- if (!visited[dep.id]) {
- visited[dep.id] = true;
- collect(dep);
- }
- });
- }
+ return iter(mod);
+ }
- visited[module.id] = true;
- collect(module);
+ 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;
+ }
- return deps;
+ 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);
+ }
+ }
+ }
+ 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 +205,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 +225,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];
+ 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 raising an error because we could be
+ // 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) {
+ if (packageJson == null || packageName !== depModuleId) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`.',
depModuleId,
@@ -198,33 +266,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,26 +291,20 @@ DependecyGraph.prototype.resolveDependency = function(
// modulePath: /x/y/a/b
var dir = path.dirname(fromModule.path);
modulePath = path.join(dir, depModuleId);
+ modulePath = browserFieldRedirect(packageJson, modulePath);
- 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];
+ dep = this._graph[modulePath] ||
+ this._graph[modulePath + '.js'] ||
+ this._graph[modulePath + '.json'];
- // 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
@@ -291,6 +328,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 +395,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 +414,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;
@@ -389,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);
@@ -406,12 +452,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) && packageJson.name != null) {
+ 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) && packageJson.name != null) {
+ delete this._packagesById[packageJson.name];
+ }
};
/**
@@ -441,6 +491,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) {
@@ -469,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 =
@@ -484,6 +542,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 +566,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 +593,14 @@ DependecyGraph.prototype._updateGraphWithModule = function(module) {
* Find the nearest package to a module.
*/
DependecyGraph.prototype._lookupPackage = function(modulePath) {
- var packageByRoot = 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), this._packageByRoot);
+};
- 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 +625,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 +645,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 +736,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 +864,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 9bc8b8b95c3766..1f8c95479bb37e 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 da68785eacd520..d7a8c0eb1b338c 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);