mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge master to 0.4-stable
This commit is contained in:
+12
-1
@@ -1,3 +1,14 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- '0.10'
|
||||
after_script:
|
||||
- curl -F "react=@build/react.js" -F "react.min=@build/react.min.js" -F "transformer=@build/JSXTransformer.js"
|
||||
-F "commit=$TRAVIS_COMMIT" -F "date=`git log --format='%ct' -1`" -F "pull_request=$TRAVIS_PULL_REQUEST"
|
||||
-F "token=$SECRET_TOKEN" $SERVER
|
||||
env:
|
||||
global:
|
||||
# SERVER
|
||||
- secure: qPvsJ46XzGrdIuPA70b55xQNGF8jcK7N1LN5CCQYYocXLa+fBrl+fTE77QvehOPhqwJXcj6kOxI+sY0KrVwV7gmq2XY2HZGWUSCxTN0SZlNIzqPA80Y7G/yOjA4PUt8LKgP+8tptyhTAY56qf+hgW8BoLiKOdztYF2p+3zXOLuA=
|
||||
# SECRET_TOKEN
|
||||
- secure: dkpPW+VnoqC/okhRdV90m36NcyBFhcwEKL3bNFExAwi0dXnFao8RoFlvnwiPlA23h2faROkMIetXlti6Aju08BgUFV+f9aL6vLyU7gUent4Nd3413zf2fwDtXIWIETg6uLnOpSykGKgCAT/hY3Q2oPLqOoY0OxfgnbqwxkxljrE=
|
||||
|
||||
@@ -41,6 +41,7 @@ require("commoner").resolve(function(id) {
|
||||
return context.getProvidedP().then(function(idToPath) {
|
||||
if (id !== "mock-modules" &&
|
||||
id !== "mocks" &&
|
||||
id !== "test/all" &&
|
||||
idToPath.hasOwnProperty("mock-modules")) {
|
||||
return source + '\nrequire("mock-modules").register(' +
|
||||
JSON.stringify(id) + ', module);\n';
|
||||
|
||||
@@ -110,7 +110,7 @@ _Mounted_ composite components also support the following methods:
|
||||
> calling `this.getDOMNode()`.
|
||||
|
||||
|
||||
## Browser Suppport and Polyfills
|
||||
## Browser Support and Polyfills
|
||||
|
||||
At Facebook, we support older browsers, including IE8. We've had polyfills in place for a long time to allow us to write forward-thinking JS. This means we don't have a bunch of hacks scattered throughout our codebase and we can still expect our code to "just work". For example, instead of seeing `+new Date()`, we can just write `Date.now()`. Since the open source React is the same as what we use internally, we've carried over this philosophy of using forward thinking JS.
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ var BallmerPeakCalculator = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {bac: 0};
|
||||
},
|
||||
handleChange: function() {
|
||||
this.setState({bac: this.refs.bac.getDOMNode().value});
|
||||
handleChange: function(event) {
|
||||
this.setState({bac: event.target.value});
|
||||
},
|
||||
render: function() {
|
||||
var bac;
|
||||
@@ -33,7 +33,7 @@ var BallmerPeakCalculator = React.createClass({
|
||||
<h4>Compute your Ballmer Peak:</h4>
|
||||
<p>
|
||||
If your BAC is{' '}
|
||||
<input ref="bac" type="text" onInput={this.handleChange} value={this.state.bac} />
|
||||
<input type="text" onChange={this.handleChange} value={this.state.bac} />
|
||||
{', '}then <b>{pct}</b> of your lines of code will have bugs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Vendored
+41
-43
@@ -7,7 +7,7 @@ var BootstrapButton = React.createClass({
|
||||
// transferPropsTo() is smart enough to merge classes provided
|
||||
// to this component.
|
||||
return this.transferPropsTo(
|
||||
<a href="javascript:;" role="button" class="btn">
|
||||
<a href="javascript:;" role="button" className="btn">
|
||||
{this.props.children}
|
||||
</a>
|
||||
);
|
||||
@@ -19,12 +19,18 @@ var BootstrapModal = React.createClass({
|
||||
// integrate with Bootstrap or jQuery!
|
||||
componentDidMount: function() {
|
||||
// When the component is added, turn it into a modal
|
||||
$(this.getDOMNode()).modal({backdrop: 'static', keyboard: false});
|
||||
$(this.getDOMNode())
|
||||
.modal({backdrop: 'static', keyboard: false, show: false})
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
// And when it's destroyed, hide it.
|
||||
$(this.getDOMNode()).off('hidden', this.handleHidden);
|
||||
},
|
||||
close: function() {
|
||||
$(this.getDOMNode()).modal('hide');
|
||||
},
|
||||
open: function() {
|
||||
$(this.getDOMNode()).modal('show');
|
||||
},
|
||||
render: function() {
|
||||
var confirmButton = null;
|
||||
var cancelButton = null;
|
||||
@@ -32,92 +38,84 @@ var BootstrapModal = React.createClass({
|
||||
if (this.props.confirm) {
|
||||
confirmButton = (
|
||||
<BootstrapButton
|
||||
onClick={this.onConfirm}
|
||||
class="btn-primary">
|
||||
onClick={this.handleConfirm}
|
||||
className="btn-primary">
|
||||
{this.props.confirm}
|
||||
</BootstrapButton>
|
||||
);
|
||||
}
|
||||
if (this.props.cancel) {
|
||||
cancelButton = (
|
||||
<BootstrapButton onClick={this.onCancel}>
|
||||
<BootstrapButton onClick={this.handleCancel}>
|
||||
{this.props.cancel}
|
||||
</BootstrapButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="modal hide fade">
|
||||
<div class="modal-header">
|
||||
<div className="modal hide fade">
|
||||
<div className="modal-header">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
onClick={this.onCancel}
|
||||
dangerouslyInsertInnerHtml={{__html: '×'}}
|
||||
className="close"
|
||||
onClick={this.handleCancel}
|
||||
dangerouslySetInnerHTML={{__html: '×'}}
|
||||
/>
|
||||
<h3>{this.props.title}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div className="modal-body">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div className="modal-footer">
|
||||
{cancelButton}
|
||||
{confirmButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
onCancel: function() {
|
||||
handleCancel: function() {
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
onConfirm: function() {
|
||||
handleConfirm: function() {
|
||||
if (this.props.onConfirm) {
|
||||
this.props.onConfirm();
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
close: function() {
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var Example = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {modalVisible: false};
|
||||
},
|
||||
toggleModal: function() {
|
||||
this.setState({modalVisible: !this.state.modalVisible});
|
||||
},
|
||||
handleCancel: function() {
|
||||
if (confirm('Are you sure you want to cancel?')) {
|
||||
this.toggleModal();
|
||||
this.refs.modal.close();
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var modal = null;
|
||||
if (this.state.modalVisible) {
|
||||
modal = (
|
||||
<BootstrapModal
|
||||
confirm="OK"
|
||||
cancel="Cancel"
|
||||
onCancel={this.handleCancel}
|
||||
onConfirm={this.toggleModal}
|
||||
title="Hello, Bootstrap!">
|
||||
This is a React component powered by jQuery and Bootstrap!
|
||||
</BootstrapModal>
|
||||
);
|
||||
}
|
||||
modal = (
|
||||
<BootstrapModal
|
||||
ref="modal"
|
||||
confirm="OK"
|
||||
cancel="Cancel"
|
||||
onCancel={this.handleCancel}
|
||||
onConfirm={this.closeModal}
|
||||
title="Hello, Bootstrap!">
|
||||
This is a React component powered by jQuery and Bootstrap!
|
||||
</BootstrapModal>
|
||||
);
|
||||
return (
|
||||
<div class="example">
|
||||
<div className="example">
|
||||
{modal}
|
||||
<BootstrapButton onClick={this.toggleModal}>Toggle modal</BootstrapButton>
|
||||
<BootstrapButton onClick={this.openModal}>Open modal</BootstrapButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
openModal: function() {
|
||||
this.refs.modal.open();
|
||||
},
|
||||
closeModal: function() {
|
||||
this.refs.modal.close();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ var jasmine = {
|
||||
|
||||
var test = {
|
||||
rootDirectory: "build/modules",
|
||||
args: ["test/all:"],
|
||||
args: ["test/all:harness"],
|
||||
requires: [
|
||||
"**/__tests__/*-test.js"
|
||||
],
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-tools",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0-alpha",
|
||||
"keywords": [
|
||||
"react",
|
||||
"jsx",
|
||||
@@ -32,7 +32,7 @@
|
||||
"url": "https://github.com/facebook/react"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt test"
|
||||
"test": "grunt build && grunt test"
|
||||
},
|
||||
"dependencies": {
|
||||
"base62": "~0.1.1",
|
||||
|
||||
@@ -8,7 +8,7 @@ gemspec = Gem::Specification.new do |s|
|
||||
s.version = package['version']
|
||||
s.license = 'Apache-2.0'
|
||||
|
||||
s.homepage = 'https://github.com/facebook/react.js'
|
||||
s.homepage = 'https://github.com/facebook/react'
|
||||
s.summary = 'Ruby bridge to JSX & the React JavaScript library.'
|
||||
|
||||
s.authors = ['Paul O’Shannessy']
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
@@ -255,7 +256,7 @@ var ReactComponent = {
|
||||
this.isMounted(),
|
||||
'getDOMNode(): A component must be mounted to have a DOM node.'
|
||||
);
|
||||
return ReactID.getNode(this._rootNodeID);
|
||||
return ReactMount.getNode(this._rootNodeID);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -381,7 +382,7 @@ var ReactComponent = {
|
||||
if (props.ref != null) {
|
||||
ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
|
||||
}
|
||||
ReactID.purgeID(this._rootNodeID);
|
||||
ReactMount.purgeID(this._rootNodeID);
|
||||
this._rootNodeID = null;
|
||||
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
|
||||
},
|
||||
@@ -498,15 +499,28 @@ var ReactComponent = {
|
||||
container && container.nodeType === 1,
|
||||
'mountComponentIntoNode(...): Target container is not a DOM element.'
|
||||
);
|
||||
var renderStart = Date.now();
|
||||
var markup = this.mountComponent(rootID, transaction);
|
||||
ReactMount.totalInstantiationTime += (Date.now() - renderStart);
|
||||
|
||||
if (shouldReuseMarkup) {
|
||||
return;
|
||||
if (ReactMarkupChecksum.canReuseMarkup(
|
||||
markup,
|
||||
getReactRootElementInContainer(container))) {
|
||||
return;
|
||||
} else {
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'React attempted to use reuse markup in a container but the ' +
|
||||
'checksum was invalid. This generally means that you are using ' +
|
||||
'server rendering and the markup generated on the server was ' +
|
||||
'not what the client was expecting. React injected new markup ' +
|
||||
'to compensate which works but you have lost many of the ' +
|
||||
'benefits of server rendering. Instead, figure out why the ' +
|
||||
'markup being generated is different on the client or server.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var injectionStart = Date.now();
|
||||
// Asynchronously inject markup by ensuring that the container is not in
|
||||
// the document when settings its `innerHTML`.
|
||||
var parent = container.parentNode;
|
||||
@@ -522,7 +536,6 @@ var ReactComponent = {
|
||||
} else {
|
||||
container.innerHTML = markup;
|
||||
}
|
||||
ReactMount.totalInjectionTime += (Date.now() - injectionStart);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
var CSSPropertyOperations = require('CSSPropertyOperations');
|
||||
var DOMChildrenOperations = require('DOMChildrenOperations');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var getTextContentAccessor = require('getTextContentAccessor');
|
||||
var invariant = require('invariant');
|
||||
@@ -65,13 +65,21 @@ var ReactDOMIDOperations = {
|
||||
* @internal
|
||||
*/
|
||||
updatePropertyByID: function(id, name, value) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
invariant(
|
||||
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
|
||||
'updatePropertyByID(...): %s',
|
||||
INVALID_PROPERTY_ERRORS[name]
|
||||
);
|
||||
DOMPropertyOperations.setValueForProperty(node, name, value);
|
||||
|
||||
// If we're updating to null or undefined, we should remove the property
|
||||
// from the DOM node instead of inadvertantly setting to a string. This
|
||||
// brings us in line with the same behavior we have on initial render.
|
||||
if (value != null) {
|
||||
DOMPropertyOperations.setValueForProperty(node, name, value);
|
||||
} else {
|
||||
DOMPropertyOperations.deleteValueForProperty(node, name);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -83,7 +91,7 @@ var ReactDOMIDOperations = {
|
||||
* @internal
|
||||
*/
|
||||
deletePropertyByID: function(id, name, value) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
invariant(
|
||||
!INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
|
||||
'updatePropertyByID(...): %s',
|
||||
@@ -120,7 +128,7 @@ var ReactDOMIDOperations = {
|
||||
* @internal
|
||||
*/
|
||||
updateStylesByID: function(id, styles) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
CSSPropertyOperations.setValueForStyles(node, styles);
|
||||
},
|
||||
|
||||
@@ -132,7 +140,7 @@ var ReactDOMIDOperations = {
|
||||
* @internal
|
||||
*/
|
||||
updateInnerHTMLByID: function(id, html) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
// HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
|
||||
// @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
|
||||
node.innerHTML = (html && html.__html || '').replace(/^ /g, ' ');
|
||||
@@ -146,7 +154,7 @@ var ReactDOMIDOperations = {
|
||||
* @internal
|
||||
*/
|
||||
updateTextContentByID: function(id, content) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
node[textContentAccessor] = content;
|
||||
},
|
||||
|
||||
@@ -159,7 +167,7 @@ var ReactDOMIDOperations = {
|
||||
* @see {Danger.dangerouslyReplaceNodeWithMarkup}
|
||||
*/
|
||||
dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
|
||||
var node = ReactID.getNode(id);
|
||||
var node = ReactMount.getNode(id);
|
||||
DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
|
||||
},
|
||||
|
||||
@@ -168,7 +176,7 @@ var ReactDOMIDOperations = {
|
||||
* Detect if any elements were removed instead of blindly purging.
|
||||
*/
|
||||
manageChildrenByParentID: function(parentID, domOperations) {
|
||||
var parent = ReactID.getNode(parentID);
|
||||
var parent = ReactMount.getNode(parentID);
|
||||
DOMChildrenOperations.manageChildren(parent, domOperations);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ var ReactDOMInput = require('ReactDOMInput');
|
||||
var ReactDOMOption = require('ReactDOMOption');
|
||||
var ReactDOMSelect = require('ReactDOMSelect');
|
||||
var ReactDOMTextarea = require('ReactDOMTextarea');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
|
||||
var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
|
||||
var DOMProperty = require('DOMProperty');
|
||||
@@ -34,8 +36,10 @@ var ChangeEventPlugin = require('ChangeEventPlugin');
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var SimpleEventPlugin = require('SimpleEventPlugin');
|
||||
var MobileSafariClickEventPlugin = require('MobileSafariClickEventPlugin');
|
||||
|
||||
function inject() {
|
||||
ReactEventEmitter.TopLevelCallbackCreator = ReactEventTopLevelCallback;
|
||||
/**
|
||||
* Inject module for resolving DOM hierarchy and plugin ordering.
|
||||
*/
|
||||
@@ -49,7 +53,8 @@ function inject() {
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
'SimpleEventPlugin': SimpleEventPlugin,
|
||||
'EnterLeaveEventPlugin': EnterLeaveEventPlugin,
|
||||
'ChangeEventPlugin': ChangeEventPlugin
|
||||
'ChangeEventPlugin': ChangeEventPlugin,
|
||||
'MobileSafariClickEventPlugin': MobileSafariClickEventPlugin
|
||||
});
|
||||
|
||||
ReactDOM.injection.injectComponentClasses({
|
||||
|
||||
@@ -244,7 +244,7 @@ var ReactEventEmitter = {
|
||||
|
||||
/**
|
||||
* React references `ReactEventTopLevelCallback` using this property in order
|
||||
* to allow dependency injection via `ensureListening`.
|
||||
* to allow dependency injection.
|
||||
*/
|
||||
TopLevelCallbackCreator: null,
|
||||
|
||||
@@ -257,17 +257,20 @@ var ReactEventEmitter = {
|
||||
* reason, and only in some cases).
|
||||
*
|
||||
* @param {boolean} touchNotMouse Listen to touch events instead of mouse.
|
||||
* @param {object} TopLevelCallbackCreator
|
||||
*/
|
||||
ensureListening: function(touchNotMouse, TopLevelCallbackCreator) {
|
||||
ensureListening: function(touchNotMouse) {
|
||||
invariant(
|
||||
ExecutionEnvironment.canUseDOM,
|
||||
'ensureListening(...): Cannot toggle event listening in a Worker ' +
|
||||
'thread. This is likely a bug in the framework. Please report ' +
|
||||
'immediately.'
|
||||
);
|
||||
invariant(
|
||||
ReactEventEmitter.TopLevelCallbackCreator,
|
||||
'ensureListening(...): Cannot be called without a top level callback ' +
|
||||
'creator being injected.'
|
||||
);
|
||||
if (!_isListening) {
|
||||
ReactEventEmitter.TopLevelCallbackCreator = TopLevelCallbackCreator;
|
||||
listenAtTopLevel(touchNotMouse);
|
||||
_isListening = true;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var getEventTarget = require('getEventTarget');
|
||||
|
||||
@@ -34,7 +33,7 @@ var _topLevelListenersEnabled = true;
|
||||
|
||||
/**
|
||||
* Top-level callback creator used to implement event handling using delegation.
|
||||
* This is used via dependency injection in `ReactEventEmitter.ensureListening`.
|
||||
* This is used via dependency injection.
|
||||
*/
|
||||
var ReactEventTopLevelCallback = {
|
||||
|
||||
@@ -73,10 +72,10 @@ var ReactEventTopLevelCallback = {
|
||||
nativeEvent.srcElement !== nativeEvent.target) {
|
||||
nativeEvent.target = nativeEvent.srcElement;
|
||||
}
|
||||
var topLevelTarget = ReactInstanceHandles.getFirstReactDOM(
|
||||
var topLevelTarget = ReactMount.getFirstReactDOM(
|
||||
getEventTarget(nativeEvent)
|
||||
) || ExecutionEnvironment.global;
|
||||
var topLevelTargetID = ReactID.getID(topLevelTarget) || '';
|
||||
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
|
||||
ReactEventEmitter.handleTopLevel(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactID
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var invariant = require('invariant');
|
||||
var ReactMount = require('ReactMount');
|
||||
var ATTR_NAME = 'data-reactid';
|
||||
var nodeCache = {};
|
||||
|
||||
/**
|
||||
* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
|
||||
* element can return its control whose name or ID equals ATTR_NAME. All
|
||||
* DOM nodes support `getAttributeNode` but this can also get called on
|
||||
* other objects so just return '' if we're given something other than a
|
||||
* DOM node (such as window).
|
||||
*
|
||||
* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
|
||||
* @return {string} ID of the supplied `domNode`.
|
||||
*/
|
||||
function getID(node) {
|
||||
var id = internalGetID(node);
|
||||
if (id) {
|
||||
if (nodeCache.hasOwnProperty(id)) {
|
||||
var cached = nodeCache[id];
|
||||
if (cached !== node) {
|
||||
invariant(
|
||||
!isValid(cached, id),
|
||||
'ReactID: Two valid but unequal nodes with the same `%s`: %s',
|
||||
ATTR_NAME, id
|
||||
);
|
||||
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
} else {
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function internalGetID(node) {
|
||||
if (node && node.getAttributeNode) {
|
||||
var attributeNode = node.getAttributeNode(ATTR_NAME);
|
||||
if (attributeNode) {
|
||||
return attributeNode.value || '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the React-specific ID of the given node.
|
||||
*
|
||||
* @param {DOMElement} node The DOM node whose ID will be set.
|
||||
* @param {string} id The value of the ID attribute.
|
||||
*/
|
||||
function setID(node, id) {
|
||||
var oldID = internalGetID(node);
|
||||
if (oldID !== id) {
|
||||
delete nodeCache[oldID];
|
||||
}
|
||||
node.setAttribute(ATTR_NAME, id);
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the node with the supplied React-generated DOM ID.
|
||||
*
|
||||
* @param {string} id A React-generated DOM ID.
|
||||
* @return {DOMElement} DOM node with the suppled `id`.
|
||||
* @internal
|
||||
*/
|
||||
function getNode(id) {
|
||||
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
|
||||
nodeCache[id] = ReactMount.findReactNodeByID(id);
|
||||
}
|
||||
return nodeCache[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* A node is "valid" if it is contained by a currently mounted container.
|
||||
*
|
||||
* This means that the node does not have to be contained by a document in
|
||||
* order to be considered valid.
|
||||
*
|
||||
* @param {?DOMElement} node The candidate DOM node.
|
||||
* @param {string} id The expected ID of the node.
|
||||
* @return {boolean} Whether the node is contained by a mounted container.
|
||||
*/
|
||||
function isValid(node, id) {
|
||||
if (node) {
|
||||
invariant(
|
||||
internalGetID(node) === id,
|
||||
'ReactID: Unexpected modification of `%s`',
|
||||
ATTR_NAME
|
||||
);
|
||||
|
||||
var container = ReactMount.findReactContainerForID(id);
|
||||
if (container && contains(container, node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function contains(ancestor, descendant) {
|
||||
if (ancestor.contains) {
|
||||
// Supported natively in virtually all browsers, but not in jsdom.
|
||||
return ancestor.contains(descendant);
|
||||
}
|
||||
|
||||
if (descendant === ancestor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descendant.nodeType === 3) {
|
||||
// If descendant is a text node, start from descendant.parentNode
|
||||
// instead, so that we can assume all ancestors worth considering are
|
||||
// element nodes with nodeType === 1.
|
||||
descendant = descendant.parentNode;
|
||||
}
|
||||
|
||||
while (descendant && descendant.nodeType === 1) {
|
||||
if (descendant === ancestor) {
|
||||
return true;
|
||||
}
|
||||
descendant = descendant.parentNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the cache to forget about one React-specific ID.
|
||||
*
|
||||
* @param {string} id The ID to forget.
|
||||
*/
|
||||
function purgeID(id) {
|
||||
delete nodeCache[id];
|
||||
}
|
||||
|
||||
exports.ATTR_NAME = ATTR_NAME;
|
||||
exports.getID = getID;
|
||||
exports.rawGetID = internalGetID;
|
||||
exports.setID = setID;
|
||||
exports.getNode = getNode;
|
||||
exports.purgeID = purgeID;
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactID = require('ReactID');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var SEPARATOR = '.';
|
||||
@@ -239,103 +237,6 @@ var ReactInstanceHandles = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the supplied `node` is rendered by React.
|
||||
*
|
||||
* @param {*} node DOM Element to check.
|
||||
* @return {boolean} True if the DOM Element appears to be rendered by React.
|
||||
* @internal
|
||||
*/
|
||||
isRenderedByReact: function(node) {
|
||||
if (node.nodeType !== 1) {
|
||||
// Not a DOMElement, therefore not a React component
|
||||
return false;
|
||||
}
|
||||
var id = ReactID.getID(node);
|
||||
return id ? id.charAt(0) === SEPARATOR : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Traverses up the ancestors of the supplied node to find a node that is a
|
||||
* DOM representation of a React component.
|
||||
*
|
||||
* @param {*} node
|
||||
* @return {?DOMEventTarget}
|
||||
* @internal
|
||||
*/
|
||||
getFirstReactDOM: function(node) {
|
||||
var current = node;
|
||||
while (current && current.parentNode !== current) {
|
||||
if (ReactInstanceHandles.isRenderedByReact(current)) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentNode;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
|
||||
* Exploits the ID naming scheme to perform the search quickly.
|
||||
*
|
||||
* @param {DOMEventTarget} ancestorNode Search from this root.
|
||||
* @pararm {string} id ID of the DOM representation of the component.
|
||||
* @return {DOMEventTarget} DOM node with the supplied `id`.
|
||||
* @internal
|
||||
*/
|
||||
findComponentRoot: function(ancestorNode, id) {
|
||||
var firstChildren = [ancestorNode.firstChild];
|
||||
var childIndex = 0;
|
||||
|
||||
while (childIndex < firstChildren.length) {
|
||||
var child = firstChildren[childIndex++];
|
||||
while (child) {
|
||||
var childID = ReactID.getID(child);
|
||||
if (childID) {
|
||||
if (id === childID) {
|
||||
return child;
|
||||
} else if (isAncestorIDOf(childID, id)) {
|
||||
// If we find a child whose ID is an ancestor of the given ID,
|
||||
// then we can be sure that we only want to search the subtree
|
||||
// rooted at this child, so we can throw out the rest of the
|
||||
// search state.
|
||||
firstChildren.length = childIndex = 0;
|
||||
firstChildren.push(child.firstChild);
|
||||
break;
|
||||
} else {
|
||||
// TODO This should not be necessary if the ID hierarchy is
|
||||
// correct, but is occasionally necessary if the DOM has been
|
||||
// modified in unexpected ways.
|
||||
firstChildren.push(child.firstChild);
|
||||
}
|
||||
} else {
|
||||
// If this child had no ID, then there's a chance that it was
|
||||
// injected automatically by the browser, as when a `<table>`
|
||||
// element sprouts an extra `<tbody>` child as a side effect of
|
||||
// `.innerHTML` parsing. Optimistically continue down this
|
||||
// branch, but not before examining the other siblings.
|
||||
firstChildren.push(child.firstChild);
|
||||
}
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Error while invoking `findComponentRoot` with the following ' +
|
||||
'ancestor node:',
|
||||
ancestorNode
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'findComponentRoot(..., %s): Unable to find element. This probably ' +
|
||||
'means the DOM was unexpectedly mutated (e.g. by the browser).',
|
||||
id,
|
||||
ReactID.getID(ancestorNode)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the DOM ID of the React component that is the root of the tree that
|
||||
* contains the React component with the supplied DOM ID.
|
||||
@@ -400,7 +301,11 @@ var ReactInstanceHandles = {
|
||||
* Exposed for unit testing.
|
||||
* @private
|
||||
*/
|
||||
_getNextDescendantID: getNextDescendantID
|
||||
_getNextDescendantID: getNextDescendantID,
|
||||
|
||||
isAncestorIDOf: isAncestorIDOf,
|
||||
|
||||
SEPARATOR: SEPARATOR
|
||||
|
||||
};
|
||||
|
||||
|
||||
+260
-25
@@ -19,10 +19,14 @@
|
||||
"use strict";
|
||||
|
||||
var invariant = require('invariant');
|
||||
var getReactRootElementInContainer = require('getReactRootElementInContainer');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
var ReactID = require('ReactID');
|
||||
|
||||
var SEPARATOR = ReactInstanceHandles.SEPARATOR;
|
||||
|
||||
var ATTR_NAME = 'data-reactid';
|
||||
var nodeCache = {};
|
||||
|
||||
var $ = require('$');
|
||||
|
||||
@@ -37,21 +41,144 @@ if (__DEV__) {
|
||||
var rootElementsByReactRootID = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DOMElement} container DOM element that may contain a React component
|
||||
* @return {?*} DOM element that may have the reactRoot ID, or null.
|
||||
*/
|
||||
function getReactRootElementInContainer(container) {
|
||||
return container && container.firstChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DOMElement} container DOM element that may contain a React component.
|
||||
* @return {?string} A "reactRoot" ID, if a React component is rendered.
|
||||
*/
|
||||
function getReactRootID(container) {
|
||||
var rootElement = getReactRootElementInContainer(container);
|
||||
return rootElement && ReactID.getID(rootElement);
|
||||
return rootElement && ReactMount.getID(rootElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form
|
||||
* element can return its control whose name or ID equals ATTR_NAME. All
|
||||
* DOM nodes support `getAttributeNode` but this can also get called on
|
||||
* other objects so just return '' if we're given something other than a
|
||||
* DOM node (such as window).
|
||||
*
|
||||
* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.
|
||||
* @return {string} ID of the supplied `domNode`.
|
||||
*/
|
||||
function getID(node) {
|
||||
var id = internalGetID(node);
|
||||
if (id) {
|
||||
if (nodeCache.hasOwnProperty(id)) {
|
||||
var cached = nodeCache[id];
|
||||
if (cached !== node) {
|
||||
invariant(
|
||||
!isValid(cached, id),
|
||||
'ReactMount: Two valid but unequal nodes with the same `%s`: %s',
|
||||
ATTR_NAME, id
|
||||
);
|
||||
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
} else {
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
function internalGetID(node) {
|
||||
// If node is something like a window, document, or text node, none of
|
||||
// which support attributes or a .getAttribute method, gracefully return
|
||||
// the empty string, as if the attribute were missing.
|
||||
return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the React-specific ID of the given node.
|
||||
*
|
||||
* @param {DOMElement} node The DOM node whose ID will be set.
|
||||
* @param {string} id The value of the ID attribute.
|
||||
*/
|
||||
function setID(node, id) {
|
||||
var oldID = internalGetID(node);
|
||||
if (oldID !== id) {
|
||||
delete nodeCache[oldID];
|
||||
}
|
||||
node.setAttribute(ATTR_NAME, id);
|
||||
nodeCache[id] = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the node with the supplied React-generated DOM ID.
|
||||
*
|
||||
* @param {string} id A React-generated DOM ID.
|
||||
* @return {DOMElement} DOM node with the suppled `id`.
|
||||
* @internal
|
||||
*/
|
||||
function getNode(id) {
|
||||
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {
|
||||
nodeCache[id] = ReactMount.findReactNodeByID(id);
|
||||
}
|
||||
return nodeCache[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* A node is "valid" if it is contained by a currently mounted container.
|
||||
*
|
||||
* This means that the node does not have to be contained by a document in
|
||||
* order to be considered valid.
|
||||
*
|
||||
* @param {?DOMElement} node The candidate DOM node.
|
||||
* @param {string} id The expected ID of the node.
|
||||
* @return {boolean} Whether the node is contained by a mounted container.
|
||||
*/
|
||||
function isValid(node, id) {
|
||||
if (node) {
|
||||
invariant(
|
||||
internalGetID(node) === id,
|
||||
'ReactMount: Unexpected modification of `%s`',
|
||||
ATTR_NAME
|
||||
);
|
||||
|
||||
var container = ReactMount.findReactContainerForID(id);
|
||||
if (container && contains(container, node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function contains(ancestor, descendant) {
|
||||
if (ancestor.contains) {
|
||||
// Supported natively in virtually all browsers, but not in jsdom.
|
||||
return ancestor.contains(descendant);
|
||||
}
|
||||
|
||||
if (descendant === ancestor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (descendant.nodeType === 3) {
|
||||
// If descendant is a text node, start from descendant.parentNode
|
||||
// instead, so that we can assume all ancestors worth considering are
|
||||
// element nodes with nodeType === 1.
|
||||
descendant = descendant.parentNode;
|
||||
}
|
||||
|
||||
while (descendant && descendant.nodeType === 1) {
|
||||
if (descendant === ancestor) {
|
||||
return true;
|
||||
}
|
||||
descendant = descendant.parentNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes the cache to forget about one React-specific ID.
|
||||
*
|
||||
* @param {string} id The ID to forget.
|
||||
*/
|
||||
function purgeID(id) {
|
||||
delete nodeCache[id];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,14 +223,10 @@ var ReactMount = {
|
||||
* Ensures that the top-level event delegation listener is set up. This will
|
||||
* be invoked some time before the first time any React component is rendered.
|
||||
*
|
||||
* @param {object} TopLevelCallbackCreator
|
||||
* @private
|
||||
*/
|
||||
prepareTopLevelEvents: function(TopLevelCallbackCreator) {
|
||||
ReactEventEmitter.ensureListening(
|
||||
ReactMount.useTouchEvents,
|
||||
TopLevelCallbackCreator
|
||||
);
|
||||
prepareTopLevelEvents: function() {
|
||||
ReactEventEmitter.ensureListening(ReactMount.useTouchEvents);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -139,7 +262,7 @@ var ReactMount = {
|
||||
* @return {string} reactRoot ID prefix
|
||||
*/
|
||||
_registerComponent: function(nextComponent, container) {
|
||||
ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
|
||||
ReactMount.prepareTopLevelEvents();
|
||||
|
||||
var reactRootID = ReactMount.registerContainer(container);
|
||||
instanceByReactRootID[reactRootID] = nextComponent;
|
||||
@@ -203,8 +326,7 @@ var ReactMount = {
|
||||
|
||||
var reactRootElement = getReactRootElementInContainer(container);
|
||||
var containerHasReactMarkup =
|
||||
reactRootElement &&
|
||||
ReactInstanceHandles.isRenderedByReact(reactRootElement);
|
||||
reactRootElement && ReactMount.isRenderedByReact(reactRootElement);
|
||||
|
||||
var shouldReuseMarkup = containerHasReactMarkup && !registeredComponent;
|
||||
|
||||
@@ -302,15 +424,15 @@ var ReactMount = {
|
||||
var rootElement = rootElementsByReactRootID[reactRootID];
|
||||
if (rootElement && rootElement.parentNode !== container) {
|
||||
invariant(
|
||||
// Call rawGetID here because getID calls isValid which calls
|
||||
// Call internalGetID here because getID calls isValid which calls
|
||||
// findReactContainerForID (this function).
|
||||
ReactID.rawGetID(rootElement) === reactRootID,
|
||||
internalGetID(rootElement) === reactRootID,
|
||||
'ReactMount: Root element ID differed from reactRootID.'
|
||||
);
|
||||
|
||||
var containerChild = container.firstChild;
|
||||
if (containerChild &&
|
||||
reactRootID === ReactID.rawGetID(containerChild)) {
|
||||
reactRootID === internalGetID(containerChild)) {
|
||||
// If the container has a new child with the same ID as the old
|
||||
// root element, then rootElementsByReactRootID[reactRootID] is
|
||||
// just stale and needs to be updated. The case that deserves a
|
||||
@@ -336,9 +458,122 @@ var ReactMount = {
|
||||
*/
|
||||
findReactNodeByID: function(id) {
|
||||
var reactRoot = ReactMount.findReactContainerForID(id);
|
||||
return ReactInstanceHandles.findComponentRoot(reactRoot, id);
|
||||
}
|
||||
return ReactMount.findComponentRoot(reactRoot, id);
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the supplied `node` is rendered by React.
|
||||
*
|
||||
* @param {*} node DOM Element to check.
|
||||
* @return {boolean} True if the DOM Element appears to be rendered by React.
|
||||
* @internal
|
||||
*/
|
||||
isRenderedByReact: function(node) {
|
||||
if (node.nodeType !== 1) {
|
||||
// Not a DOMElement, therefore not a React component
|
||||
return false;
|
||||
}
|
||||
var id = ReactMount.getID(node);
|
||||
return id ? id.charAt(0) === SEPARATOR : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Traverses up the ancestors of the supplied node to find a node that is a
|
||||
* DOM representation of a React component.
|
||||
*
|
||||
* @param {*} node
|
||||
* @return {?DOMEventTarget}
|
||||
* @internal
|
||||
*/
|
||||
getFirstReactDOM: function(node) {
|
||||
var current = node;
|
||||
while (current && current.parentNode !== current) {
|
||||
if (ReactMount.isRenderedByReact(current)) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentNode;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
|
||||
* Exploits the ID naming scheme to perform the search quickly.
|
||||
*
|
||||
* @param {DOMEventTarget} ancestorNode Search from this root.
|
||||
* @pararm {string} id ID of the DOM representation of the component.
|
||||
* @return {DOMEventTarget} DOM node with the supplied `id`.
|
||||
* @internal
|
||||
*/
|
||||
findComponentRoot: function(ancestorNode, id) {
|
||||
var firstChildren = [ancestorNode.firstChild];
|
||||
var childIndex = 0;
|
||||
|
||||
while (childIndex < firstChildren.length) {
|
||||
var child = firstChildren[childIndex++];
|
||||
while (child) {
|
||||
var childID = ReactMount.getID(child);
|
||||
if (childID) {
|
||||
if (id === childID) {
|
||||
return child;
|
||||
} else if (ReactInstanceHandles.isAncestorIDOf(childID, id)) {
|
||||
// If we find a child whose ID is an ancestor of the given ID,
|
||||
// then we can be sure that we only want to search the subtree
|
||||
// rooted at this child, so we can throw out the rest of the
|
||||
// search state.
|
||||
firstChildren.length = childIndex = 0;
|
||||
firstChildren.push(child.firstChild);
|
||||
break;
|
||||
} else {
|
||||
// TODO This should not be necessary if the ID hierarchy is
|
||||
// correct, but is occasionally necessary if the DOM has been
|
||||
// modified in unexpected ways.
|
||||
firstChildren.push(child.firstChild);
|
||||
}
|
||||
} else {
|
||||
// If this child had no ID, then there's a chance that it was
|
||||
// injected automatically by the browser, as when a `<table>`
|
||||
// element sprouts an extra `<tbody>` child as a side effect of
|
||||
// `.innerHTML` parsing. Optimistically continue down this
|
||||
// branch, but not before examining the other siblings.
|
||||
firstChildren.push(child.firstChild);
|
||||
}
|
||||
child = child.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
'Error while invoking `findComponentRoot` with the following ' +
|
||||
'ancestor node:',
|
||||
ancestorNode
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
false,
|
||||
'findComponentRoot(..., %s): Unable to find element. This probably ' +
|
||||
'means the DOM was unexpectedly mutated (e.g. by the browser).',
|
||||
id,
|
||||
ReactMount.getID(ancestorNode)
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* React ID utilities.
|
||||
*/
|
||||
|
||||
ATTR_NAME: ATTR_NAME,
|
||||
|
||||
getID: getID,
|
||||
|
||||
setID: setID,
|
||||
|
||||
getNode: getNode,
|
||||
|
||||
purgeID: purgeID,
|
||||
|
||||
injection: {}
|
||||
};
|
||||
|
||||
module.exports = ReactMount;
|
||||
|
||||
@@ -25,7 +25,7 @@ var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var escapeTextForBrowser = require('escapeTextForBrowser');
|
||||
var flattenChildren = require('flattenChildren');
|
||||
@@ -136,7 +136,7 @@ ReactNativeComponent.Mixin = {
|
||||
}
|
||||
|
||||
var escapedID = escapeTextForBrowser(this._rootNodeID);
|
||||
return ret + ' ' + ReactID.ATTR_NAME + '="' + escapedID + '">';
|
||||
return ret + ' ' + ReactMount.ATTR_NAME + '="' + escapedID + '">';
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"use strict";
|
||||
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var escapeTextForBrowser = require('escapeTextForBrowser');
|
||||
var mixInto = require('mixInto');
|
||||
@@ -58,7 +58,7 @@ mixInto(ReactTextComponent, {
|
||||
mountComponent: function(rootID) {
|
||||
ReactComponent.Mixin.mountComponent.call(this, rootID);
|
||||
return (
|
||||
'<span ' + ReactID.ATTR_NAME + '="' + rootID + '">' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="' + rootID + '">' +
|
||||
escapeTextForBrowser(this.props.text) +
|
||||
'</span>'
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ function batchedUpdates(callback) {
|
||||
component.performUpdateIfNecessary();
|
||||
if (callbacks) {
|
||||
for (var j = 0; j < callbacks.length; j++) {
|
||||
callbacks[j]();
|
||||
callbacks[j].call(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ var React;
|
||||
var ReactCurrentOwner;
|
||||
var ReactPropTypes;
|
||||
var ReactTestUtils;
|
||||
var ReactID;
|
||||
var ReactMount;
|
||||
var ReactDoNotBindDeprecated;
|
||||
|
||||
var cx;
|
||||
@@ -41,7 +41,7 @@ describe('ReactCompositeComponent', function() {
|
||||
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
|
||||
ReactPropTypes = require('ReactPropTypes');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ReactID = require('ReactID');
|
||||
ReactMount = require('ReactMount');
|
||||
|
||||
MorphingComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
@@ -129,11 +129,13 @@ describe('ReactCompositeComponent', function() {
|
||||
// rerender
|
||||
instance.setProps({renderAnchor: true, anchorClassOn: false});
|
||||
var anchorID = instance.getAnchorID();
|
||||
var actualDOMAnchorNode = ReactID.getNode(anchorID);
|
||||
var actualDOMAnchorNode = ReactMount.getNode(anchorID);
|
||||
expect(actualDOMAnchorNode.className).toBe('');
|
||||
});
|
||||
|
||||
it('should auto bind methods and values correctly', function() {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
var ComponentClass = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {valueToReturn: 'hi'};
|
||||
@@ -168,7 +170,9 @@ describe('ReactCompositeComponent', function() {
|
||||
// Next, prove that once mounted, the scope is bound correctly to the actual
|
||||
// component.
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
expect(console.warn.argsForCall.length).toBe(0);
|
||||
var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
|
||||
expect(console.warn.argsForCall.length).toBe(1);
|
||||
var autoBound = instance.methodAutoBound;
|
||||
var explicitlyNotBound = instance.methodExplicitlyNotBound;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ var React = require('React');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
var React = require('React');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
describe('ref swapping', function() {
|
||||
// TODO: uncomment this test once we can run in phantom, which
|
||||
@@ -61,7 +61,7 @@ describe('ref swapping', function() {
|
||||
var argDiv = ReactTestUtils.renderIntoDocument(
|
||||
ReactDOM.div(null, 'child')
|
||||
);
|
||||
var argNode = ReactID.getNode(argDiv._rootNodeID);
|
||||
var argNode = ReactMount.getNode(argDiv._rootNodeID);
|
||||
expect(argNode.innerHTML).toBe('child');
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('ref swapping', function() {
|
||||
var conflictDiv = ReactTestUtils.renderIntoDocument(
|
||||
ReactDOM.div({children: 'fakechild'}, 'child')
|
||||
);
|
||||
var conflictNode = ReactID.getNode(conflictDiv._rootNodeID);
|
||||
var conflictNode = ReactMount.getNode(conflictDiv._rootNodeID);
|
||||
expect(conflictNode.innerHTML).toBe('child');
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('ref swapping', function() {
|
||||
theBird: <div class="bird" />
|
||||
}
|
||||
});
|
||||
var root = ReactID.getNode(myDiv._rootNodeID);
|
||||
var root = ReactMount.getNode(myDiv._rootNodeID);
|
||||
var dog = root.childNodes[0];
|
||||
expect(dog.className).toBe('bigdog');
|
||||
});
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
describe('ReactDOMIDOperations', function() {
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactDOMIDOperations = require('ReactDOMIDOperations');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
var keyOf = require('keyOf');
|
||||
|
||||
it('should disallow updating special properties', function() {
|
||||
spyOn(ReactID, "getNode");
|
||||
spyOn(ReactMount, "getNode");
|
||||
spyOn(DOMPropertyOperations, "setValueForProperty");
|
||||
|
||||
expect(function() {
|
||||
@@ -39,7 +39,7 @@ describe('ReactDOMIDOperations', function() {
|
||||
}).toThrow();
|
||||
|
||||
expect(
|
||||
ReactID.getNode.argsForCall[0][0]
|
||||
ReactMount.getNode.argsForCall[0][0]
|
||||
).toBe('testID');
|
||||
|
||||
expect(
|
||||
@@ -49,7 +49,7 @@ describe('ReactDOMIDOperations', function() {
|
||||
|
||||
it('should update innerHTML and special-case whitespace', function() {
|
||||
var stubNode = document.createElement('div');
|
||||
spyOn(ReactID, "getNode").andReturn(stubNode);
|
||||
spyOn(ReactMount, "getNode").andReturn(stubNode);
|
||||
|
||||
ReactDOMIDOperations.updateInnerHTMLByID(
|
||||
'testID',
|
||||
@@ -57,7 +57,7 @@ describe('ReactDOMIDOperations', function() {
|
||||
);
|
||||
|
||||
expect(
|
||||
ReactID.getNode.argsForCall[0][0]
|
||||
ReactMount.getNode.argsForCall[0][0]
|
||||
).toBe('testID');
|
||||
|
||||
expect(stubNode.innerHTML).toBe(' testContent');
|
||||
|
||||
@@ -22,7 +22,7 @@ require('mock-modules')
|
||||
.dontMock('BrowserScroll')
|
||||
.dontMock('CallbackRegistry')
|
||||
.dontMock('EventPluginHub')
|
||||
.dontMock('ReactID')
|
||||
.dontMock('ReactMount')
|
||||
.dontMock('ReactEventEmitter')
|
||||
.dontMock('ReactInstanceHandles')
|
||||
.dontMock('EventPluginHub')
|
||||
@@ -35,11 +35,10 @@ var keyOf = require('keyOf');
|
||||
var mocks = require('mocks');
|
||||
|
||||
var EventPluginHub;
|
||||
var ReactID = require('ReactID');
|
||||
var getID = ReactID.getID;
|
||||
var setID = ReactID.setID;
|
||||
var ReactMount = require('ReactMount');
|
||||
var getID = ReactMount.getID;
|
||||
var setID = ReactMount.setID;
|
||||
var ReactEventEmitter;
|
||||
var ReactEventTopLevelCallback;
|
||||
var ReactTestUtils;
|
||||
var TapEventPlugin;
|
||||
|
||||
@@ -89,15 +88,14 @@ describe('ReactEventEmitter', function() {
|
||||
require('mock-modules').dumpCache();
|
||||
EventPluginHub = require('EventPluginHub');
|
||||
TapEventPlugin = require('TapEventPlugin');
|
||||
ReactID = require('ReactID');
|
||||
getID = ReactID.getID;
|
||||
setID = ReactID.setID;
|
||||
ReactMount = require('ReactMount');
|
||||
getID = ReactMount.getID;
|
||||
setID = ReactMount.setID;
|
||||
ReactEventEmitter = require('ReactEventEmitter');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
idCallOrder = [];
|
||||
tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
|
||||
ReactEventEmitter.ensureListening(false, ReactEventTopLevelCallback);
|
||||
ReactEventEmitter.ensureListening(false);
|
||||
EventPluginHub.injection.injectEventPluginsByName({
|
||||
TapEventPlugin: TapEventPlugin
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
var reactComponentExpect;
|
||||
var ReactID;
|
||||
var ReactMount;
|
||||
|
||||
describe('ReactIdentity', function() {
|
||||
|
||||
@@ -31,12 +31,12 @@ describe('ReactIdentity', function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
ReactID = require('ReactID');
|
||||
ReactMount = require('ReactMount');
|
||||
});
|
||||
|
||||
var idExp = /^\.r\[.+?\](.*)$/;
|
||||
function checkId(child, expectedId) {
|
||||
var actual = idExp.exec(ReactID.getID(child));
|
||||
var actual = idExp.exec(ReactMount.getID(child));
|
||||
var expected = idExp.exec(expectedId);
|
||||
expect(actual).toBeTruthy();
|
||||
expect(expected).toBeTruthy();
|
||||
@@ -281,11 +281,11 @@ describe('ReactIdentity', function() {
|
||||
|
||||
React.renderComponent(wrapped, document.createElement('div'));
|
||||
|
||||
var beforeID = ReactID.getID(wrapped.getDOMNode().firstChild);
|
||||
var beforeID = ReactMount.getID(wrapped.getDOMNode().firstChild);
|
||||
|
||||
wrapped.swap();
|
||||
|
||||
var afterID = ReactID.getID(wrapped.getDOMNode().firstChild);
|
||||
var afterID = ReactMount.getID(wrapped.getDOMNode().firstChild);
|
||||
|
||||
expect(beforeID).not.toEqual(afterID);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
var React = require('React');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
/**
|
||||
* Ensure that all callbacks are invoked, passing this unique argument.
|
||||
@@ -78,7 +78,7 @@ describe('ReactInstanceHandles', function() {
|
||||
describe('isRenderedByReact', function() {
|
||||
it('should not crash on text nodes', function() {
|
||||
expect(function() {
|
||||
ReactInstanceHandles.isRenderedByReact(document.createTextNode('yolo'));
|
||||
ReactMount.isRenderedByReact(document.createTextNode('yolo'));
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -91,14 +91,14 @@ describe('ReactInstanceHandles', function() {
|
||||
parentNode.appendChild(childNodeA);
|
||||
parentNode.appendChild(childNodeB);
|
||||
|
||||
ReactID.setID(parentNode, '.react[0]');
|
||||
ReactID.setID(childNodeA, '.react[0].0');
|
||||
ReactID.setID(childNodeB, '.react[0].0:1');
|
||||
ReactMount.setID(parentNode, '.react[0]');
|
||||
ReactMount.setID(childNodeA, '.react[0].0');
|
||||
ReactMount.setID(childNodeB, '.react[0].0:1');
|
||||
|
||||
expect(
|
||||
ReactInstanceHandles.findComponentRoot(
|
||||
ReactMount.findComponentRoot(
|
||||
parentNode,
|
||||
ReactID.getID(childNodeB)
|
||||
ReactMount.getID(childNodeB)
|
||||
)
|
||||
).toBe(childNodeB);
|
||||
});
|
||||
@@ -110,14 +110,14 @@ describe('ReactInstanceHandles', function() {
|
||||
parentNode.appendChild(childNodeA);
|
||||
parentNode.appendChild(childNodeB);
|
||||
|
||||
ReactID.setID(parentNode, '.react[0]');
|
||||
ReactMount.setID(parentNode, '.react[0]');
|
||||
// No ID on `childNodeA`.
|
||||
ReactID.setID(childNodeB, '.react[0].0:1');
|
||||
ReactMount.setID(childNodeB, '.react[0].0:1');
|
||||
|
||||
expect(
|
||||
ReactInstanceHandles.findComponentRoot(
|
||||
ReactMount.findComponentRoot(
|
||||
parentNode,
|
||||
ReactID.getID(childNodeB)
|
||||
ReactMount.getID(childNodeB)
|
||||
)
|
||||
).toBe(childNodeB);
|
||||
});
|
||||
@@ -129,25 +129,30 @@ describe('ReactInstanceHandles', function() {
|
||||
parentNode.appendChild(childNodeA);
|
||||
childNodeA.appendChild(childNodeB);
|
||||
|
||||
ReactID.setID(parentNode, '.react[0]');
|
||||
ReactMount.setID(parentNode, '.react[0]');
|
||||
// No ID on `childNodeA`, it was "rendered by the browser".
|
||||
ReactID.setID(childNodeB, '.react[0].1:0');
|
||||
ReactMount.setID(childNodeB, '.react[0].1:0');
|
||||
|
||||
expect(ReactInstanceHandles.findComponentRoot(
|
||||
expect(ReactMount.findComponentRoot(
|
||||
parentNode,
|
||||
ReactID.getID(childNodeB)
|
||||
ReactMount.getID(childNodeB)
|
||||
)).toBe(childNodeB);
|
||||
|
||||
spyOn(console, 'error');
|
||||
expect(console.error.argsForCall.length).toBe(0);
|
||||
|
||||
expect(function() {
|
||||
ReactInstanceHandles.findComponentRoot(
|
||||
ReactMount.findComponentRoot(
|
||||
parentNode,
|
||||
ReactID.getID(childNodeB) + ":junk"
|
||||
ReactMount.getID(childNodeB) + ":junk"
|
||||
);
|
||||
}).toThrow(
|
||||
'Invariant Violation: findComponentRoot(..., .react[0].1:0:junk): ' +
|
||||
'Unable to find element. This probably means the DOM was ' +
|
||||
'unexpectedly mutated (e.g. by the browser).'
|
||||
);
|
||||
|
||||
expect(console.error.argsForCall.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ require('mock-modules');
|
||||
|
||||
var React = require('React');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var objMapKeyVal = require('objMapKeyVal');
|
||||
|
||||
@@ -191,7 +191,7 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
|
||||
var i;
|
||||
var orderedDomIds = [];
|
||||
for (i=0; i < statusDisplayNodes.length; i++) {
|
||||
orderedDomIds.push(ReactID.getID(statusDisplayNodes[i]));
|
||||
orderedDomIds.push(ReactMount.getID(statusDisplayNodes[i]));
|
||||
}
|
||||
|
||||
var orderedLogicalIds = [];
|
||||
|
||||
@@ -317,7 +317,7 @@ describe('ReactNativeComponent', function() {
|
||||
it("should clean up listeners", function() {
|
||||
var React = require('React');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var container = document.createElement('div');
|
||||
document.documentElement.appendChild(container);
|
||||
@@ -327,7 +327,7 @@ describe('ReactNativeComponent', function() {
|
||||
React.renderComponent(instance, container);
|
||||
|
||||
var rootNode = instance.getDOMNode();
|
||||
var rootNodeID = ReactID.getID(rootNode);
|
||||
var rootNodeID = ReactMount.getID(rootNode);
|
||||
expect(
|
||||
ReactEventEmitter.getListener(rootNodeID, 'onClick')
|
||||
).toBe(callback);
|
||||
|
||||
@@ -237,6 +237,7 @@ describe('ReactUpdates', function() {
|
||||
ReactUpdates.batchedUpdates(function() {
|
||||
instance.setState({x: 1}, function() {
|
||||
instance.setState({x: 2}, function() {
|
||||
expect(this).toBe(instance);
|
||||
innerCallbackRun = true;
|
||||
expect(instance.state.x).toBe(2);
|
||||
expect(updateCount).toBe(2);
|
||||
|
||||
@@ -44,7 +44,11 @@ var ClickCounter = React.createClass({
|
||||
var i;
|
||||
for (i=0; i < this.state.count; i++) {
|
||||
children.push(
|
||||
<div className="clickLogDiv" ref={"clickLog" + i} />
|
||||
<div
|
||||
className="clickLogDiv"
|
||||
key={"clickLog" + i}
|
||||
ref={"clickLog" + i}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule getReactRootElementInContainer
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @param {DOMElement} container DOM element that may contain a React component
|
||||
* @return {?*} DOM element that may have the reactRoot ID, or null.
|
||||
*/
|
||||
function getReactRootElementInContainer(container) {
|
||||
return container && container.firstChild;
|
||||
}
|
||||
|
||||
module.exports = getReactRootElementInContainer;
|
||||
@@ -38,6 +38,7 @@ var DefaultDOMPropertyConfig = {
|
||||
action: null,
|
||||
ajaxify: MUST_USE_ATTRIBUTE,
|
||||
allowFullScreen: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
allowTransparency: MUST_USE_ATTRIBUTE,
|
||||
alt: null,
|
||||
autoComplete: null,
|
||||
autoFocus: HAS_BOOLEAN_VALUE,
|
||||
@@ -56,6 +57,7 @@ var DefaultDOMPropertyConfig = {
|
||||
disabled: MUST_USE_PROPERTY | HAS_BOOLEAN_VALUE,
|
||||
draggable: null,
|
||||
encType: null,
|
||||
frameBorder: MUST_USE_ATTRIBUTE,
|
||||
height: MUST_USE_ATTRIBUTE,
|
||||
hidden: MUST_USE_ATTRIBUTE | HAS_BOOLEAN_VALUE,
|
||||
href: null,
|
||||
|
||||
@@ -62,9 +62,12 @@ describe('ReactDOMTextarea', function() {
|
||||
});
|
||||
|
||||
it('should treat children like `defaultValue`', function() {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
var stub = <textarea>giraffe</textarea>;
|
||||
var node = renderTextarea(stub);
|
||||
|
||||
expect(console.warn.argsForCall.length).toBe(1);
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
// Changing children should do nothing, it functions like `defaultValue`.
|
||||
@@ -73,21 +76,29 @@ describe('ReactDOMTextarea', function() {
|
||||
});
|
||||
|
||||
it('should allow numbers as children', function() {
|
||||
spyOn(console, 'warn');
|
||||
var node = renderTextarea(<textarea>{17}</textarea>);
|
||||
expect(console.warn.argsForCall.length).toBe(1);
|
||||
expect(node.value).toBe('17');
|
||||
});
|
||||
|
||||
it("should throw with multiple or invalid children", function() {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea>{'hello'}{'there'}</textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expect(console.warn.argsForCall.length).toBe(1);
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea><strong /></textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expect(console.warn.argsForCall.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactMarkupChecksum
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var adler32 = require('adler32');
|
||||
|
||||
var ReactMarkupChecksum = {
|
||||
CHECKSUM_ATTR_NAME: 'data-react-checksum',
|
||||
|
||||
/**
|
||||
* @param {string} markup Markup string
|
||||
* @return {string} Markup string with checksum attribute attached
|
||||
*/
|
||||
addChecksumToMarkup: function(markup) {
|
||||
var checksum = adler32(markup);
|
||||
return markup.replace(
|
||||
'>',
|
||||
' ' + ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="' + checksum + '">'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} markup to use
|
||||
* @param {DOMElement} element root React element
|
||||
* @returns {boolean} whether or not the markup is the same
|
||||
*/
|
||||
canReuseMarkup: function(markup, element) {
|
||||
var existingChecksum = element.getAttribute(
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME
|
||||
);
|
||||
existingChecksum = existingChecksum && parseInt(existingChecksum, 10);
|
||||
var markupChecksum = adler32(markup);
|
||||
return markupChecksum === existingChecksum;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactMarkupChecksum;
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
|
||||
@@ -33,7 +34,9 @@ function renderComponentToString(component, callback) {
|
||||
transaction.reinitializeTransaction();
|
||||
try {
|
||||
transaction.perform(function() {
|
||||
callback(component.mountComponent(id, transaction));
|
||||
var markup = component.mountComponent(id, transaction);
|
||||
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
|
||||
callback(markup);
|
||||
}, null);
|
||||
} finally {
|
||||
ReactReconcileTransaction.release(transaction);
|
||||
|
||||
@@ -24,25 +24,30 @@
|
||||
require('mock-modules')
|
||||
.dontMock('ExecutionEnvironment')
|
||||
.dontMock('React')
|
||||
.dontMock('ReactID')
|
||||
.dontMock('ReactMount')
|
||||
.dontMock('ReactServerRendering')
|
||||
.dontMock('ReactTestUtils');
|
||||
.dontMock('ReactTestUtils')
|
||||
.dontMock('ReactMarkupChecksum');
|
||||
|
||||
var mocks = require('mocks');
|
||||
|
||||
var React;
|
||||
var ReactID;
|
||||
var ReactMount;
|
||||
var ReactTestUtils;
|
||||
var ReactServerRendering;
|
||||
var ReactMarkupChecksum;
|
||||
var ExecutionEnvironment;
|
||||
|
||||
describe('ReactServerRendering', function() {
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
React = require('React');
|
||||
ReactID = require('ReactID');
|
||||
ReactMount = require('ReactMount');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
ExecutionEnvironment.canUseDOM = false;
|
||||
ReactServerRendering = require('ReactServerRendering');
|
||||
ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
});
|
||||
|
||||
it('should generate simple markup', function() {
|
||||
@@ -54,7 +59,8 @@ describe('ReactServerRendering', function() {
|
||||
}
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">hello world</span>'
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,10 +83,11 @@ describe('ReactServerRendering', function() {
|
||||
}
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<div ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">My name is </span>' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">child</span>' +
|
||||
'<div ' + ReactMount.ATTR_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+">My name is </span>' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+">child</span>' +
|
||||
'</span>' +
|
||||
'</div>'
|
||||
);
|
||||
@@ -130,9 +137,10 @@ describe('ReactServerRendering', function() {
|
||||
);
|
||||
|
||||
expect(response).toMatch(
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">Component name: </span>' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">TestComponent</span>' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+">Component name: </span>' +
|
||||
'<span ' + ReactMount.ATTR_NAME + '="[^"]+">TestComponent</span>' +
|
||||
'</span>'
|
||||
);
|
||||
expect(lifecycle).toEqual(
|
||||
@@ -148,7 +156,7 @@ describe('ReactServerRendering', function() {
|
||||
var numClicks = 0;
|
||||
|
||||
var TestComponent = React.createClass({
|
||||
componentWillMount: function() {
|
||||
componentDidMount: function() {
|
||||
mountCount++;
|
||||
},
|
||||
click: function() {
|
||||
@@ -179,18 +187,41 @@ describe('ReactServerRendering', function() {
|
||||
expect(mountCount).toEqual(2);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
|
||||
// Now kill the node and render it on top of the old markup, as if
|
||||
// Now kill the node and render it on top of server-rendered markup, as if
|
||||
// we used server rendering. We should mount again, but the markup should be
|
||||
// unchanged.
|
||||
lastMarkup = element.innerHTML;
|
||||
// unchanged. We will append a sentinel at the end of innerHTML to be sure
|
||||
// that innerHTML was not changed.
|
||||
React.unmountAndReleaseReactRootNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
element.innerHTML = lastMarkup;
|
||||
// NOTE: we pass a different name here. This is to ensure that the markup
|
||||
// being generated is not replaced.
|
||||
var instance = React.renderComponent(<TestComponent name="y" />, element);
|
||||
|
||||
ExecutionEnvironment.canUseDOM = false;
|
||||
ReactServerRendering.renderComponentToString(
|
||||
<TestComponent name="x" />,
|
||||
function(markup) {
|
||||
lastMarkup = markup;
|
||||
}
|
||||
);
|
||||
ExecutionEnvironment.canUseDOM = true;
|
||||
|
||||
element.innerHTML = lastMarkup + ' __sentinel__';
|
||||
|
||||
React.renderComponent(<TestComponent name="x" />, element);
|
||||
expect(mountCount).toEqual(3);
|
||||
expect(element.innerHTML).toEqual(lastMarkup);
|
||||
expect(element.innerHTML.indexOf('__sentinel__') > -1).toBe(true);
|
||||
React.unmountAndReleaseReactRootNode(element);
|
||||
expect(element.innerHTML).toEqual('');
|
||||
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
var _warn = console.warn;
|
||||
console.warn = mocks.getMockFunction();
|
||||
element.innerHTML = lastMarkup;
|
||||
var instance = React.renderComponent(<TestComponent name="y" />, element);
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
expect(element.innerHTML.length > 0).toBe(true);
|
||||
expect(element.innerHTML).not.toEqual(lastMarkup);
|
||||
console.warn = _warn;
|
||||
|
||||
// Ensure the events system works
|
||||
expect(numClicks).toEqual(0);
|
||||
|
||||
@@ -68,6 +68,9 @@ function SyntheticEvent(dispatchConfig, dispatchMarker, nativeEvent) {
|
||||
|
||||
var Interface = this.constructor.Interface;
|
||||
for (var propName in Interface) {
|
||||
if (!Interface.hasOwnProperty(propName)) {
|
||||
continue;
|
||||
}
|
||||
var normalize = Interface[propName];
|
||||
if (normalize) {
|
||||
this[propName] = normalize(nativeEvent);
|
||||
|
||||
@@ -35,7 +35,8 @@ var DefaultEventPluginOrder = [
|
||||
keyOf({TapEventPlugin: null}),
|
||||
keyOf({EnterLeaveEventPlugin: null}),
|
||||
keyOf({ChangeEventPlugin: null}),
|
||||
keyOf({AnalyticsEventPlugin: null})
|
||||
keyOf({AnalyticsEventPlugin: null}),
|
||||
keyOf({MobileSafariClickEventPlugin: null})
|
||||
];
|
||||
|
||||
module.exports = DefaultEventPluginOrder;
|
||||
|
||||
@@ -22,14 +22,13 @@
|
||||
var EventConstants = require('EventConstants');
|
||||
var EventPropagators = require('EventPropagators');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var SyntheticMouseEvent = require('SyntheticMouseEvent');
|
||||
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
var keyOf = require('keyOf');
|
||||
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
var getFirstReactDOM = ReactInstanceHandles.getFirstReactDOM;
|
||||
var getFirstReactDOM = ReactMount.getFirstReactDOM;
|
||||
|
||||
var eventTypes = {
|
||||
mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
|
||||
@@ -85,8 +84,8 @@ var EnterLeaveEventPlugin = {
|
||||
return null;
|
||||
}
|
||||
|
||||
var fromID = from ? ReactID.getID(from) : '';
|
||||
var toID = to ? ReactID.getID(to) : '';
|
||||
var fromID = from ? ReactMount.getID(from) : '';
|
||||
var toID = to ? ReactMount.getID(to) : '';
|
||||
|
||||
var leave = SyntheticMouseEvent.getPooled(
|
||||
eventTypes.mouseLeave,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule MobileSafariClickEventPlugin
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EventConstants = require('EventConstants');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
|
||||
var topLevelTypes = EventConstants.topLevelTypes;
|
||||
|
||||
/**
|
||||
* Mobile Safari does not fire properly bubble click events on non-interactive
|
||||
* elements, which means delegated click listeners do not fire. The workaround
|
||||
* for this bug involves attaching an empty click listener on the target node.
|
||||
*
|
||||
* This particular plugin works around the bug by attaching an empty click
|
||||
* listener on `touchstart` (which does fire on every element).
|
||||
*/
|
||||
var MobileSafariClickEventPlugin = {
|
||||
|
||||
eventTypes: null,
|
||||
|
||||
/**
|
||||
* @param {string} topLevelType Record from `EventConstants`.
|
||||
* @param {DOMEventTarget} topLevelTarget The listening component root node.
|
||||
* @param {string} topLevelTargetID ID of `topLevelTarget`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {*} An accumulation of synthetic events.
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType,
|
||||
topLevelTarget,
|
||||
topLevelTargetID,
|
||||
nativeEvent) {
|
||||
if (topLevelType === topLevelTypes.topTouchStart) {
|
||||
var target = nativeEvent.target;
|
||||
if (target && !target.onclick) {
|
||||
target.onclick = emptyFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = MobileSafariClickEventPlugin;
|
||||
@@ -27,7 +27,6 @@ describe('AnalyticsEventPlugin', function() {
|
||||
var EventPluginRegistry;
|
||||
var React;
|
||||
var ReactEventEmitter;
|
||||
var ReactEventTopLevelCallback;
|
||||
var ReactTestUtils;
|
||||
|
||||
var DefaultEventPluginOrder;
|
||||
@@ -42,7 +41,6 @@ describe('AnalyticsEventPlugin', function() {
|
||||
EventPluginRegistry = require('EventPluginRegistry');
|
||||
React = require('React');
|
||||
ReactEventEmitter = require('ReactEventEmitter');
|
||||
ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
EventPluginRegistry._resetEventPlugins();
|
||||
@@ -63,7 +61,7 @@ describe('AnalyticsEventPlugin', function() {
|
||||
'ChangeEventPlugin': ChangeEventPlugin
|
||||
});
|
||||
|
||||
ReactEventEmitter.ensureListening(false, ReactEventTopLevelCallback);
|
||||
ReactEventEmitter.ensureListening(false);
|
||||
});
|
||||
|
||||
it('should count events correctly', function() {
|
||||
|
||||
@@ -21,7 +21,7 @@ var React = require('React');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactEventEmitter = require('ReactEventEmitter');
|
||||
var ReactTextComponent = require('ReactTextComponent');
|
||||
var ReactID = require('ReactID');
|
||||
var ReactMount = require('ReactMount');
|
||||
|
||||
var mergeInto = require('mergeInto');
|
||||
|
||||
@@ -222,10 +222,10 @@ var ReactTestUtils = {
|
||||
ReactEventEmitter.TopLevelCallbackCreator.createTopLevelCallback(
|
||||
topLevelType
|
||||
);
|
||||
var node = ReactID.getNode(reactRootID);
|
||||
var node = ReactMount.getNode(reactRootID);
|
||||
fakeNativeEvent.target = node;
|
||||
/* jsdom is returning nodes without id's - fixing that issue here. */
|
||||
ReactID.setID(node, reactRootID);
|
||||
ReactMount.setID(node, reactRootID);
|
||||
virtualHandler(fakeNativeEvent);
|
||||
},
|
||||
|
||||
|
||||
+28
-34
@@ -6,49 +6,30 @@ var Ap = Array.prototype;
|
||||
var slice = Ap.slice;
|
||||
var Fp = Function.prototype;
|
||||
|
||||
var global = Function("return this")();
|
||||
global.require = require;
|
||||
|
||||
if (!Fp.bind) {
|
||||
// PhantomJS doesn't support Function.prototype.bind natively, so
|
||||
// polyfill it whenever this module is required.
|
||||
Fp.bind = function(context) {
|
||||
var func = this;
|
||||
var args = slice.call(arguments, 1);
|
||||
var bound;
|
||||
|
||||
if (func.prototype) {
|
||||
if (args.length > 0) {
|
||||
bound = function() {
|
||||
return func.apply(
|
||||
!(this instanceof func) && context || this,
|
||||
args.concat(slice.call(arguments))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
bound = function() {
|
||||
return func.apply(
|
||||
!(this instanceof func) && context || this,
|
||||
arguments
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
bound.prototype = Object.create(func.prototype);
|
||||
|
||||
} else if (args.length > 0) {
|
||||
bound = function() {
|
||||
return func.apply(
|
||||
context || this,
|
||||
args.concat(slice.call(arguments))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
bound = function() {
|
||||
return func.apply(context || this, arguments);
|
||||
};
|
||||
function bound() {
|
||||
var invokedAsConstructor = func.prototype && (this instanceof func);
|
||||
return func.apply(
|
||||
// Ignore the context parameter when invoking the bound function
|
||||
// as a constructor. Note that this includes not only constructor
|
||||
// invocations using the new keyword but also calls to base class
|
||||
// constructors such as BaseClass.call(this, ...) or super(...).
|
||||
!invokedAsConstructor && context || this,
|
||||
args.concat(slice.call(arguments))
|
||||
);
|
||||
}
|
||||
|
||||
// The bound function must share the .prototype of the unbound
|
||||
// function so that any object created by one constructor will count
|
||||
// as an instance of both constructors.
|
||||
bound.prototype = func.prototype;
|
||||
|
||||
return bound;
|
||||
};
|
||||
}
|
||||
@@ -58,3 +39,16 @@ require("reactComponentExpect");
|
||||
require("mocks");
|
||||
require("mock-modules");
|
||||
require("./mock-timers");
|
||||
|
||||
exports.enableTest = function(testID) {
|
||||
require("../" + testID);
|
||||
};
|
||||
|
||||
exports.removeNextSiblings = function(node) {
|
||||
var parent = node && node.parentNode;
|
||||
if (parent) {
|
||||
while (node.nextSibling) {
|
||||
parent.removeChild(node.nextSibling);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,112 +14,111 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ImmutableObject
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var keyMirror = require('keyMirror');
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
var mergeInto = require('mergeInto');
|
||||
var mergeHelpers = require('mergeHelpers');
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
|
||||
var isTerminal = mergeHelpers.isTerminal;
|
||||
|
||||
/**
|
||||
* Simple wrapper around javascript key/value objects that provide a dev time
|
||||
* guarantee of immutability (assuming all modules that may potentially mutate
|
||||
* include a "use strict" declaration). Retaining immutability requires CPU
|
||||
* cycles (in order to perform the freeze), but this computation can be avoided
|
||||
* in production. The fact that mutation attempts in __DEV__ will be caught,
|
||||
* allows us to reasonably assume that mutation on those objects won't even be
|
||||
* attempted in production. This means that an object being an instanceof
|
||||
* `ImmutableObject` implies that the object may never change.
|
||||
*
|
||||
* TODO: Require strict mode in source files that use ImmutableObject (lint
|
||||
* rule).
|
||||
*
|
||||
* @class ImmutableObject
|
||||
* Wrapper around JavaScript objects that provide a guarantee of immutability at
|
||||
* developer time when strict mode is used. The extra computations required to
|
||||
* enforce immutability is stripped out in production for performance reasons.
|
||||
*/
|
||||
|
||||
var ERRORS;
|
||||
var ImmutableObject;
|
||||
|
||||
if (__DEV__) {
|
||||
ERRORS = {
|
||||
INVALID_MAP_SET_ARG: 'You have attempted to set fields on an object that ' +
|
||||
'is not an instance of ImmutableObject'
|
||||
};
|
||||
function assertImmutableObject(immutableObject) {
|
||||
invariant(
|
||||
immutableObject instanceof ImmutableObject,
|
||||
'ImmutableObject: Attempted to set fields on an object that is not an ' +
|
||||
'instance of ImmutableObject.'
|
||||
);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
/**
|
||||
* Constructs an instance of `ImmutableObject`.
|
||||
*
|
||||
* @param {!Object} initMap The initial set of properties.
|
||||
* @param {?object} initialProperties The initial set of properties.
|
||||
* @constructor
|
||||
*/
|
||||
ImmutableObject = function(initMap) {
|
||||
mergeInto(this, initMap);
|
||||
deepFreeze(this, initMap);
|
||||
ImmutableObject = function ImmutableObject(initialProperties) {
|
||||
mergeInto(this, initialProperties);
|
||||
deepFreeze(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Objects that are instances of `ImmutableObject` are assumed to be deep
|
||||
* frozen.
|
||||
* @param {!Object} o The object to deep freeze.
|
||||
* @return {!boolean} Whether or not deep freeze is needed.
|
||||
*/
|
||||
var shouldRecurseFreeze = function(o) {
|
||||
return (typeof o) === 'object' &&
|
||||
!(o instanceof ImmutableObject) && o !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Freezes an object `o` deeply. Invokes `shouldRecurseFreeze` to determine if
|
||||
* further freezing is needed.
|
||||
* Checks if an object should be deep frozen. Instances of `ImmutableObject`
|
||||
* are assumed to have already been deep frozen.
|
||||
*
|
||||
* @param {!Object} o The object to freeze.
|
||||
* @param {*} object The object to check.
|
||||
* @return {boolean} Whether or not deep freeze is needed.
|
||||
*/
|
||||
var deepFreeze = function(o) {
|
||||
var prop;
|
||||
Object.freeze(o); // First freeze the object.
|
||||
for (prop in o) {
|
||||
var field = o[prop];
|
||||
if (o.hasOwnProperty(prop) && shouldRecurseFreeze(field)) {
|
||||
var shouldRecurseFreeze = function(object) {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
!(object instanceof ImmutableObject) &&
|
||||
object !== null
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Freezes the supplied object deeply.
|
||||
*
|
||||
* @param {*} object The object to freeze.
|
||||
*/
|
||||
var deepFreeze = function(object) {
|
||||
Object.freeze(object); // First freeze the object.
|
||||
for (var prop in object) {
|
||||
var field = object[prop];
|
||||
if (object.hasOwnProperty(prop) && shouldRecurseFreeze(field)) {
|
||||
deepFreeze(field);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new ImmutableObject that is that is the same as the parameter
|
||||
* `immutableObject` but with the differences specified in `put`.
|
||||
* @param {!ImmutableObject} ImmutableObject The ImmutableObject object to set
|
||||
* fields on.
|
||||
* @param {!Object} put Subset of fields to merge into the returned result.
|
||||
* @return {!ImmutableObject} The result of merging in `put` fields.
|
||||
* Returns a new ImmutableObject that is identical to the supplied object but
|
||||
* with the supplied changes, `put`.
|
||||
*
|
||||
* @param {ImmutableObject} immutableObject Starting object.
|
||||
* @param {?object} put Fields to merge into the object.
|
||||
* @return {ImmutableObject} The result of merging in `put` fields.
|
||||
*/
|
||||
ImmutableObject.set = function(immutableObject, put) {
|
||||
throwIf(
|
||||
!(immutableObject instanceof ImmutableObject),
|
||||
ERRORS.INVALID_MAP_SET_ARG
|
||||
);
|
||||
assertImmutableObject(immutableObject);
|
||||
var totalNewFields = merge(immutableObject, put);
|
||||
return new ImmutableObject(totalNewFields);
|
||||
};
|
||||
|
||||
} else {
|
||||
ERRORS = keyMirror({INVALID_MAP_SET_ARG: null});
|
||||
|
||||
ImmutableObject = function(initMap) {
|
||||
mergeInto(this, initMap);
|
||||
/**
|
||||
* Constructs an instance of `ImmutableObject`.
|
||||
*
|
||||
* @param {?object} initialProperties The initial set of properties.
|
||||
* @constructor
|
||||
*/
|
||||
ImmutableObject = function ImmutableObject(initialProperties) {
|
||||
mergeInto(this, initialProperties);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new ImmutableObject that is identical to the supplied object but
|
||||
* with the supplied changes, `put`.
|
||||
*
|
||||
* @param {ImmutableObject} immutableObject Starting object.
|
||||
* @param {?object} put Fields to merge into the object.
|
||||
* @return {ImmutableObject} The result of merging in `put` fields.
|
||||
*/
|
||||
ImmutableObject.set = function(immutableObject, put) {
|
||||
throwIf(
|
||||
!(immutableObject instanceof ImmutableObject),
|
||||
ERRORS.INVALID_MAP_SET_ARG
|
||||
);
|
||||
assertImmutableObject(immutableObject);
|
||||
var newMap = new ImmutableObject(immutableObject);
|
||||
mergeInto(newMap, put);
|
||||
return newMap;
|
||||
@@ -127,8 +126,12 @@ if (__DEV__) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`
|
||||
* @see ImmutableObject.set
|
||||
* Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`.
|
||||
*
|
||||
* @param {ImmutableObject} immutableObject Object on which to set field.
|
||||
* @param {string} fieldName Name of the field to set.
|
||||
* @param {*} putField Value of the field to set.
|
||||
* @return {ImmutableObject} [description]
|
||||
*/
|
||||
ImmutableObject.setField = function(immutableObject, fieldName, putField) {
|
||||
var put = {};
|
||||
@@ -137,15 +140,15 @@ ImmutableObject.setField = function(immutableObject, fieldName, putField) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new ImmutableObject that is that is the same as the parameter
|
||||
* `immutableObject` but with the differences specified in `put` recursively
|
||||
* applied.
|
||||
* Returns a new ImmutableObject that is identical to the supplied object but
|
||||
* with the supplied changes recursively applied.
|
||||
*
|
||||
* @param {ImmutableObject} immutableObject Object on which to set fields.
|
||||
* @param {object} put Fields to merge into the object.
|
||||
* @return {ImmutableObject} The result of merging in `put` fields.
|
||||
*/
|
||||
ImmutableObject.setDeep = function(immutableObject, put) {
|
||||
throwIf(
|
||||
!(immutableObject instanceof ImmutableObject),
|
||||
ERRORS.INVALID_MAP_SET_ARG
|
||||
);
|
||||
assertImmutableObject(immutableObject);
|
||||
return _setDeep(immutableObject, put);
|
||||
};
|
||||
|
||||
|
||||
+71
-71
@@ -18,41 +18,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var invariant = require('invariant');
|
||||
var mixInto = require('mixInto');
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var PREFIX = 'key:';
|
||||
|
||||
var ARRAY_MUST_CB = 'ARRAY_MUST_CB';
|
||||
var INVALID_KEY = 'INVALID_KEY';
|
||||
var DUPLICATE_KEY = 'DUPLICATE_KEY';
|
||||
var OPERATION_ARGS = 'OPERATION_ARGS';
|
||||
var RANGE_ARGS = 'RANGE_ARGS';
|
||||
var NO_EXIST = 'NO_EXIST';
|
||||
|
||||
if (__DEV__) {
|
||||
ARRAY_MUST_CB =
|
||||
'If providing an array to an OrderedMap constructor, you must provide ' +
|
||||
'a callback that may determine the unique key for each entry. ' +
|
||||
'The key that you return from the callback should uniquely define that ' +
|
||||
'entity. What is returned from that function must answer the question: ' +
|
||||
'"If you were to shuffle the array of entities, how would you be able to ' +
|
||||
'determine the identity of the Array entry?"';
|
||||
INVALID_KEY =
|
||||
'Key must be non-empty, non-null, string or number';
|
||||
DUPLICATE_KEY =
|
||||
'At creation time, you must extract out IDs that are unique.';
|
||||
OPERATION_ARGS =
|
||||
'Invalid argument type for construction or operation on OrderedMap. ' +
|
||||
'from/merge accepts another OrderedMap. fromArray accepts an Array ' +
|
||||
'and a callback to extract a unique ID.';
|
||||
RANGE_ARGS =
|
||||
'OrderedMap.mapRange requires end of range to be >= start, start to be ' +
|
||||
'>= 0 and end < length.';
|
||||
NO_EXIST =
|
||||
'The requested key does not exist in the OrderedMap';
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to extract a backing object from an initialization `Array`, allowing
|
||||
* the caller to assist in resolving the unique ID for each entry via the
|
||||
@@ -69,9 +39,12 @@ function extractObjectFromArray(arr, keyExtractor) {
|
||||
for (var i=0; i < arr.length; i++) {
|
||||
var item = arr[i];
|
||||
var key = keyExtractor(item);
|
||||
validatePublicKey(key);
|
||||
assertValidPublicKey(key);
|
||||
var normalizedKey = PREFIX + key;
|
||||
throwIf(normalizedKey in normalizedObj, DUPLICATE_KEY);
|
||||
invariant(
|
||||
!(normalizedKey in normalizedObj),
|
||||
'OrderedMap: IDs returned by the key extraction function must be unique.'
|
||||
);
|
||||
normalizedObj[normalizedKey] = item;
|
||||
}
|
||||
return normalizedObj;
|
||||
@@ -112,19 +85,16 @@ function OrderedMapImpl(normalizedObj, computedLength) {
|
||||
* Validates a "public" key - that is, one that the public facing API supplies.
|
||||
* The key is then normalized for internal storage. In order to be considered
|
||||
* valid, all keys must be non-empty, defined, non-null strings or numbers.
|
||||
* Since this already costs a function invocation, will avoid additional call to
|
||||
* `throwIf`.
|
||||
*
|
||||
* @param {string?} key Validates that the key is suitable for use in a
|
||||
* `OrderedMap`.
|
||||
* @throws Error if key is not appropriate for use in `OrderedMap`.
|
||||
*/
|
||||
function validatePublicKey(key) {
|
||||
var isEmpty = key === '';
|
||||
var correctKeyType = typeof key === 'string' || typeof key === 'number';
|
||||
if (isEmpty || !correctKeyType) {
|
||||
throw new Error(INVALID_KEY);
|
||||
}
|
||||
function assertValidPublicKey(key) {
|
||||
invariant(
|
||||
key !== '' && (typeof key === 'string' || typeof key === 'number'),
|
||||
'OrderedMap: Key must be non-empty, non-null string or number.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,15 +106,16 @@ function validatePublicKey(key) {
|
||||
* exceeded.
|
||||
* @return {void} description
|
||||
*/
|
||||
function validateRangeIndices(start, length, actualLen) {
|
||||
var startType = typeof start;
|
||||
var lengthType = typeof length;
|
||||
var invalid =
|
||||
startType !== 'number' || lengthType !== 'number' ||
|
||||
length < 0 || start < 0 || start + length > actualLen;
|
||||
if (invalid) {
|
||||
throw new Error(RANGE_ARGS);
|
||||
}
|
||||
function assertValidRangeIndices(start, length, actualLen) {
|
||||
invariant(
|
||||
typeof start === 'number' &&
|
||||
typeof length === 'number' &&
|
||||
length >= 0 &&
|
||||
start >= 0 &&
|
||||
start + length <= actualLen,
|
||||
'OrderedMap: `mapRange` and `forEachRange` expect non-negative start and ' +
|
||||
'length arguments within the bounds of the instance.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,10 +127,12 @@ function validateRangeIndices(start, length, actualLen) {
|
||||
* @return {OrderedMap} new `OrderedMap` that results in merging `a` and `b`.
|
||||
*/
|
||||
function _fromNormalizedObjects(a, b) {
|
||||
// Second optional, both must be a Plain Old JavaScript Object.
|
||||
var invalidArgs =
|
||||
!a || a.constructor !== Object || (b && b.constructor !== Object);
|
||||
throwIf(invalidArgs, OPERATION_ARGS);
|
||||
// Second optional, both must be plain JavaScript objects.
|
||||
invariant(
|
||||
a && a.constructor === Object && (!b || b.constructor === Object),
|
||||
'OrderedMap: Corrupted instance of OrderedMap detected.'
|
||||
);
|
||||
|
||||
var newSet = {};
|
||||
var length = 0;
|
||||
var key;
|
||||
@@ -202,7 +175,7 @@ var OrderedMapMethods = {
|
||||
* @throws Error if provided known invalid key.
|
||||
*/
|
||||
has: function(key) {
|
||||
validatePublicKey(key);
|
||||
assertValidPublicKey(key);
|
||||
var normalizedKey = PREFIX + key;
|
||||
return normalizedKey in this._normalizedObj;
|
||||
},
|
||||
@@ -216,7 +189,7 @@ var OrderedMapMethods = {
|
||||
* @throws Error if provided known invalid key.
|
||||
*/
|
||||
get: function(key) {
|
||||
validatePublicKey(key);
|
||||
assertValidPublicKey(key);
|
||||
var normalizedKey = PREFIX + key;
|
||||
return this.has(key) ? this._normalizedObj[normalizedKey] : undefined;
|
||||
},
|
||||
@@ -234,7 +207,10 @@ var OrderedMapMethods = {
|
||||
* merge.
|
||||
*/
|
||||
merge: function(orderedMap) {
|
||||
throwIf(!(orderedMap instanceof OrderedMapImpl), OPERATION_ARGS);
|
||||
invariant(
|
||||
orderedMap instanceof OrderedMapImpl,
|
||||
'OrderedMap.merge(...): Expected an OrderedMap instance.'
|
||||
);
|
||||
return _fromNormalizedObjects(
|
||||
this._normalizedObj,
|
||||
orderedMap._normalizedObj
|
||||
@@ -266,7 +242,7 @@ var OrderedMapMethods = {
|
||||
var thisSet = this._normalizedObj;
|
||||
var newSet = {};
|
||||
var i = 0;
|
||||
validateRangeIndices(start, length, this.length);
|
||||
assertValidRangeIndices(start, length, this.length);
|
||||
var end = start + length - 1;
|
||||
for (var key in thisSet) {
|
||||
if (thisSet.hasOwnProperty(key)) {
|
||||
@@ -322,7 +298,7 @@ var OrderedMapMethods = {
|
||||
},
|
||||
|
||||
forEachRange: function(cb, start, length, context) {
|
||||
validateRangeIndices(start, length, this.length);
|
||||
assertValidRangeIndices(start, length, this.length);
|
||||
var thisSet = this._normalizedObj;
|
||||
var i = 0;
|
||||
var end = start + length - 1;
|
||||
@@ -349,18 +325,29 @@ var OrderedMapMethods = {
|
||||
mapKeyRange: function(cb, startKey, endKey, context) {
|
||||
var startIndex = this.indexOfKey(startKey);
|
||||
var endIndex = this.indexOfKey(endKey);
|
||||
if (endIndex < startIndex) {
|
||||
throw new Error(RANGE_ARGS);
|
||||
}
|
||||
invariant(
|
||||
startIndex !== undefined && endIndex !== undefined,
|
||||
'mapKeyRange must be given keys that are present.'
|
||||
);
|
||||
invariant(
|
||||
endIndex >= startIndex,
|
||||
'OrderedMap.mapKeyRange(...): `endKey` must not come before `startIndex`.'
|
||||
);
|
||||
return this.mapRange(cb, startIndex, (endIndex - startIndex) + 1, context);
|
||||
},
|
||||
|
||||
forEachKeyRange: function(cb, startKey, endKey, context) {
|
||||
var startIndex = this.indexOfKey(startKey);
|
||||
var endIndex = this.indexOfKey(endKey);
|
||||
if (endIndex < startIndex) {
|
||||
throw new Error(RANGE_ARGS);
|
||||
}
|
||||
invariant(
|
||||
startIndex !== undefined && endIndex !== undefined,
|
||||
'forEachKeyRange must be given keys that are present.'
|
||||
);
|
||||
invariant(
|
||||
endIndex >= startIndex,
|
||||
'OrderedMap.forEachKeyRange(...): `endKey` must not come before ' +
|
||||
'`startIndex`.'
|
||||
);
|
||||
this.forEachRange(cb, startIndex, (endIndex - startIndex) + 1, context);
|
||||
},
|
||||
|
||||
@@ -404,7 +391,11 @@ var OrderedMapMethods = {
|
||||
*/
|
||||
nthKeyAfter: function(key, n) {
|
||||
var curIndex = this.indexOfKey(key);
|
||||
throwIf(curIndex === undefined, NO_EXIST);
|
||||
invariant(
|
||||
curIndex !== undefined,
|
||||
'OrderedMap.nthKeyAfter: The key `%s` does not exist in this instance.',
|
||||
key
|
||||
);
|
||||
return this.keyAtIndex(curIndex + n);
|
||||
},
|
||||
|
||||
@@ -425,7 +416,7 @@ var OrderedMapMethods = {
|
||||
* key is not found.
|
||||
*/
|
||||
indexOfKey: function(key) {
|
||||
validatePublicKey(key);
|
||||
assertValidPublicKey(key);
|
||||
var normalizedKey = PREFIX + key;
|
||||
var computedPositions = this._getOrComputePositions();
|
||||
var computedPosition = computedPositions.indexByKey[normalizedKey];
|
||||
@@ -496,14 +487,23 @@ mixInto(OrderedMapImpl, OrderedMapMethods);
|
||||
|
||||
var OrderedMap = {
|
||||
from: function(orderedMap) {
|
||||
var invalidArg = !(orderedMap instanceof OrderedMapImpl);
|
||||
throwIf(invalidArg, OPERATION_ARGS);
|
||||
invariant(
|
||||
orderedMap instanceof OrderedMapImpl,
|
||||
'OrderedMap.from(...): Expected an OrderedMap instance.'
|
||||
);
|
||||
return _fromNormalizedObjects(orderedMap._normalizedObj, null);
|
||||
},
|
||||
|
||||
fromArray: function(arr, keyExtractor) {
|
||||
throwIf(!Array.isArray(arr), OPERATION_ARGS);
|
||||
throwIf(!keyExtractor, ARRAY_MUST_CB);
|
||||
invariant(
|
||||
Array.isArray(arr),
|
||||
'OrderedMap.fromArray(...): First argument must be an array.'
|
||||
);
|
||||
invariant(
|
||||
typeof keyExtractor === 'function',
|
||||
'OrderedMap.fromArray(...): Second argument must be a function used ' +
|
||||
'to determine the unique key for each entry.'
|
||||
);
|
||||
return new OrderedMapImpl(
|
||||
extractObjectFromArray(arr, keyExtractor),
|
||||
arr.length
|
||||
|
||||
+37
-38
@@ -18,23 +18,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var DUAL_TRANSACTION = 'DUAL_TRANSACTION';
|
||||
var MISSING_TRANSACTION = 'MISSING_TRANSACTION';
|
||||
if (__DEV__) {
|
||||
DUAL_TRANSACTION =
|
||||
'Cannot initialize transaction when there is already an outstanding ' +
|
||||
'transaction. Common causes of this are trying to render a component ' +
|
||||
'when you are already rendering a component or attempting a state ' +
|
||||
'transition while in a render function. Another possibility is that ' +
|
||||
'you are rendering new content (or state transitioning) in a ' +
|
||||
'componentDidRender callback. If this is not the case, please report the ' +
|
||||
'issue immediately.';
|
||||
|
||||
MISSING_TRANSACTION =
|
||||
'Cannot close transaction when there is none open.';
|
||||
}
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* `Transaction` creates a black box that is able to wrap any method such that
|
||||
@@ -156,26 +140,33 @@ var Mixin = {
|
||||
* @return Return value from `method`.
|
||||
*/
|
||||
perform: function(method, scope, a, b, c, d, e, f) {
|
||||
throwIf(this.isInTransaction(), DUAL_TRANSACTION);
|
||||
invariant(
|
||||
!this.isInTransaction(),
|
||||
'Transaction.perform(...): Cannot initialize a transaction when there ' +
|
||||
'is already an outstanding transaction.'
|
||||
);
|
||||
var memberStart = Date.now();
|
||||
var err = null;
|
||||
var errorToThrow = null;
|
||||
var ret;
|
||||
try {
|
||||
this.initializeAll();
|
||||
ret = method.call(scope, a, b, c, d, e, f);
|
||||
} catch (ie_requires_catch) {
|
||||
err = ie_requires_catch;
|
||||
} catch (error) {
|
||||
// IE8 requires `catch` in order to use `finally`.
|
||||
errorToThrow = error;
|
||||
} finally {
|
||||
var memberEnd = Date.now();
|
||||
this.methodInvocationTime += (memberEnd - memberStart);
|
||||
try {
|
||||
this.closeAll();
|
||||
} catch (closeAllErr) {
|
||||
err = err || closeAllErr;
|
||||
} catch (closeError) {
|
||||
// If `method` throws, prefer to show that stack trace over any thrown
|
||||
// by invoking `closeAll`.
|
||||
errorToThrow = errorToThrow || closeError;
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
throw err;
|
||||
if (errorToThrow) {
|
||||
throw errorToThrow;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
@@ -184,15 +175,17 @@ var Mixin = {
|
||||
this._isInTransaction = true;
|
||||
var transactionWrappers = this.transactionWrappers;
|
||||
var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
|
||||
var err = null;
|
||||
var errorToThrow = null;
|
||||
for (var i = 0; i < transactionWrappers.length; i++) {
|
||||
var initStart = Date.now();
|
||||
var wrapper = transactionWrappers[i];
|
||||
try {
|
||||
this.wrapperInitData[i] =
|
||||
wrapper.initialize ? wrapper.initialize.call(this) : null;
|
||||
} catch (initErr) {
|
||||
err = err || initErr; // Remember the first error.
|
||||
this.wrapperInitData[i] = wrapper.initialize ?
|
||||
wrapper.initialize.call(this) :
|
||||
null;
|
||||
} catch (initError) {
|
||||
// Prefer to show the stack trace of the first error.
|
||||
errorToThrow = errorToThrow || initError;
|
||||
this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
|
||||
} finally {
|
||||
var curInitTime = wrapperInitTimes[i];
|
||||
@@ -200,8 +193,8 @@ var Mixin = {
|
||||
wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
throw err;
|
||||
if (errorToThrow) {
|
||||
throw errorToThrow;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -212,10 +205,13 @@ var Mixin = {
|
||||
* invoked).
|
||||
*/
|
||||
closeAll: function() {
|
||||
throwIf(!this.isInTransaction(), MISSING_TRANSACTION);
|
||||
invariant(
|
||||
this.isInTransaction(),
|
||||
'Transaction.closeAll(): Cannot close transaction when none are open.'
|
||||
);
|
||||
var transactionWrappers = this.transactionWrappers;
|
||||
var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
|
||||
var err = null;
|
||||
var errorToThrow = null;
|
||||
for (var i = 0; i < transactionWrappers.length; i++) {
|
||||
var wrapper = transactionWrappers[i];
|
||||
var closeStart = Date.now();
|
||||
@@ -224,8 +220,9 @@ var Mixin = {
|
||||
if (initData !== Transaction.OBSERVED_ERROR) {
|
||||
wrapper.close && wrapper.close.call(this, initData);
|
||||
}
|
||||
} catch (closeErr) {
|
||||
err = err || closeErr; // Remember the first error.
|
||||
} catch (closeError) {
|
||||
// Prefer to show the stack trace of the first error.
|
||||
errorToThrow = errorToThrow || closeError;
|
||||
} finally {
|
||||
var closeEnd = Date.now();
|
||||
var curCloseTime = wrapperCloseTimes[i];
|
||||
@@ -234,14 +231,16 @@ var Mixin = {
|
||||
}
|
||||
this.wrapperInitData.length = 0;
|
||||
this._isInTransaction = false;
|
||||
if (err) {
|
||||
throw err;
|
||||
if (errorToThrow) {
|
||||
throw errorToThrow;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var Transaction = {
|
||||
|
||||
Mixin: Mixin,
|
||||
|
||||
/**
|
||||
* Token to look for to determine if an error occured.
|
||||
*/
|
||||
|
||||
@@ -739,10 +739,16 @@ describe('OrderedMap', function() {
|
||||
}).not.toThrow();
|
||||
expect(function() {
|
||||
om.mapKeyRange(duplicate, 'x' , 3, scope);
|
||||
}).not.toThrow();
|
||||
}).toThrow(
|
||||
'Invariant Violation: mapKeyRange must be given keys ' +
|
||||
'that are present.'
|
||||
);
|
||||
expect(function() {
|
||||
om.forEachKeyRange(duplicate, 'x', 3, scope);
|
||||
}).not.toThrow();
|
||||
}).toThrow(
|
||||
'Invariant Violation: forEachKeyRange must be given keys ' +
|
||||
'that are present.'
|
||||
);
|
||||
|
||||
expect(function() {
|
||||
om.mapRange(duplicate, 0, 4, scope);
|
||||
|
||||
+22
-38
@@ -18,50 +18,34 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var INVALID_ARGS = 'INVALID_ACCUM_ARGS';
|
||||
|
||||
if (__DEV__) {
|
||||
INVALID_ARGS =
|
||||
'accumulate requires non empty (non-null, defined) next ' +
|
||||
'values. All arrays accumulated must not contain any empty items.';
|
||||
}
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Accumulates items that must never be empty, into a result in a manner that
|
||||
* conserves memory - avoiding allocation of arrays until they are needed. The
|
||||
* accumulation may start and/or end up being a single element or an array
|
||||
* depending on the total count (if greater than one, an array is allocated).
|
||||
* Handles most common case first (starting with an empty current value and
|
||||
* acquiring one).
|
||||
* @return {Accumulation} An accumulation which is either a single item or an
|
||||
* Array of items.
|
||||
* Accumulates items that must not be null or undefined.
|
||||
*
|
||||
* This is used to conserve memory by avoiding array allocations.
|
||||
*
|
||||
* @return {*|array<*>} An accumulation of items.
|
||||
*/
|
||||
function accumulate(cur, next) {
|
||||
var curValIsEmpty = cur == null; // Will test for emptiness (null/undef)
|
||||
var nextValIsEmpty = next === null;
|
||||
if (__DEV__) {
|
||||
throwIf(nextValIsEmpty, INVALID_ARGS);
|
||||
}
|
||||
if (nextValIsEmpty) {
|
||||
return cur;
|
||||
function accumulate(current, next) {
|
||||
invariant(
|
||||
next != null,
|
||||
'accumulate(...): Accumulated items must be not be null or undefined.'
|
||||
);
|
||||
if (current == null) {
|
||||
return next;
|
||||
} else {
|
||||
if (curValIsEmpty) {
|
||||
return next;
|
||||
// Both are not empty. Warning: Never call x.concat(y) when you are not
|
||||
// certain that x is an Array (x could be a string with concat method).
|
||||
var currentIsArray = Array.isArray(current);
|
||||
var nextIsArray = Array.isArray(next);
|
||||
if (currentIsArray) {
|
||||
return current.concat(next);
|
||||
} else {
|
||||
// Both are not empty. Warning: Never call x.concat(y) when you are not
|
||||
// certain that x is an Array (x could be a string with concat method).
|
||||
var curIsArray = Array.isArray(cur);
|
||||
var nextIsArray = Array.isArray(next);
|
||||
if (curIsArray) {
|
||||
return cur.concat(next);
|
||||
if (nextIsArray) {
|
||||
return [current].concat(next);
|
||||
} else {
|
||||
if (nextIsArray) {
|
||||
return [cur].concat(next);
|
||||
} else {
|
||||
return [cur, next];
|
||||
}
|
||||
return [current, next];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule adler32
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var MOD = 65521;
|
||||
|
||||
// This is a clean-room implementation of adler32 designed for detecting
|
||||
// if markup is not what we expect it to be. It does not need to be
|
||||
// cryptographically strong, only reasonable good at detecting if markup
|
||||
// generated on the server is different than that on the client.
|
||||
function adler32(data) {
|
||||
var a = 1;
|
||||
var b = 0;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
a = (a + data.charCodeAt(i)) % MOD;
|
||||
b = (b + a) % MOD;
|
||||
}
|
||||
return a | (b << 16);
|
||||
}
|
||||
|
||||
module.exports = adler32;
|
||||
@@ -14,19 +14,12 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule escapeTextForBrowser
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var ESCAPE_TYPE_ERR;
|
||||
|
||||
if (__DEV__) {
|
||||
ESCAPE_TYPE_ERR =
|
||||
'The React core has attempted to escape content that is of a ' +
|
||||
'mysterious type (object etc) Escaping only works on numbers and strings';
|
||||
}
|
||||
var invariant = require('invariant');
|
||||
|
||||
var ESCAPE_LOOKUP = {
|
||||
"&": "&",
|
||||
@@ -41,13 +34,19 @@ function escaper(match) {
|
||||
return ESCAPE_LOOKUP[match];
|
||||
}
|
||||
|
||||
var escapeTextForBrowser = function (text) {
|
||||
/**
|
||||
* Escapes text to prevent scripting attacks.
|
||||
*
|
||||
* @param {number|string} text Text value to escape.
|
||||
* @return {string} An escaped string.
|
||||
*/
|
||||
function escapeTextForBrowser(text) {
|
||||
var type = typeof text;
|
||||
var invalid = type === 'object';
|
||||
if (__DEV__) {
|
||||
throwIf(invalid, ESCAPE_TYPE_ERR);
|
||||
}
|
||||
if (text === '' || invalid) {
|
||||
invariant(
|
||||
type !== 'object',
|
||||
'escapeTextForBrowser(...): Attempted to escape an object.'
|
||||
);
|
||||
if (text === '') {
|
||||
return '';
|
||||
} else {
|
||||
if (type === 'string') {
|
||||
@@ -56,6 +55,6 @@ var escapeTextForBrowser = function (text) {
|
||||
return (''+text).replace(/[&><"'\/]/g, escaper);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = escapeTextForBrowser;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
var invariant = require('invariant');
|
||||
var traverseAllChildren = require('traverseAllChildren');
|
||||
|
||||
/**
|
||||
@@ -27,14 +27,14 @@ var traverseAllChildren = require('traverseAllChildren');
|
||||
* @param {!string} name String name of key path to child.
|
||||
*/
|
||||
function flattenSingleChildIntoContext(traverseContext, child, name) {
|
||||
// We found a component instance
|
||||
// We found a component instance.
|
||||
var result = traverseContext;
|
||||
if (__DEV__) {
|
||||
throwIf(
|
||||
result.hasOwnProperty(name),
|
||||
traverseAllChildren.DUPLICATE_KEY_ERROR
|
||||
);
|
||||
}
|
||||
invariant(
|
||||
!result.hasOwnProperty(name),
|
||||
'flattenChildren(...): Encountered two children with the same key, `%s`. ' +
|
||||
'Children keys must be unique.',
|
||||
name
|
||||
);
|
||||
result[name] = child;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
|
||||
* @return {!object} flattened children keyed by name.
|
||||
*/
|
||||
function flattenChildren(children) {
|
||||
if (children === null || children === undefined) {
|
||||
if (children == null) {
|
||||
return children;
|
||||
}
|
||||
var result = {};
|
||||
|
||||
+20
-18
@@ -14,36 +14,38 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule keyMirror
|
||||
* @typechecks static-only
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var NOT_OBJECT_ERROR = 'NOT_OBJECT_ERROR';
|
||||
if (__DEV__) {
|
||||
NOT_OBJECT_ERROR = 'keyMirror only works on objects';
|
||||
}
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Utility for constructing enums with keys being equal to the associated
|
||||
* values, even when using advanced key crushing. This is useful for debugging,
|
||||
* but also for using the values themselves as lookups into the enum.
|
||||
* Example:
|
||||
* var COLORS = keyMirror({blue: null, red: null});
|
||||
* var myColor = COLORS.blue;
|
||||
* var isColorValid = !!COLORS[myColor]
|
||||
* Constructs an enumeration with keys equal to their value.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* var COLORS = keyMirror({blue: null, red: null});
|
||||
* var myColor = COLORS.blue;
|
||||
* var isColorValid = !!COLORS[myColor];
|
||||
*
|
||||
* The last line could not be performed if the values of the generated enum were
|
||||
* not equal to their keys.
|
||||
* Input: {key1: val1, key2: val2}
|
||||
* Output: {key1: key1, key2: key2}
|
||||
*
|
||||
* Input: {key1: val1, key2: val2}
|
||||
* Output: {key1: key1, key2: key2}
|
||||
*
|
||||
* @param {object} obj
|
||||
* @return {object}
|
||||
*/
|
||||
var keyMirror = function(obj) {
|
||||
var ret = {};
|
||||
var key;
|
||||
|
||||
throwIf(!(obj instanceof Object) || Array.isArray(obj), NOT_OBJECT_ERROR);
|
||||
|
||||
invariant(
|
||||
obj instanceof Object && !Array.isArray(obj),
|
||||
'keyMirror(...): Argument must be an object.'
|
||||
);
|
||||
for (key in obj) {
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
continue;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
var PooledClass = require('PooledClass');
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
var invariant = require('invariant');
|
||||
var traverseAllChildren = require('traverseAllChildren');
|
||||
|
||||
var threeArgumentPooler = PooledClass.threeArgumentPooler;
|
||||
@@ -50,13 +50,13 @@ function mapSingleChildIntoContext(traverseContext, child, name, i) {
|
||||
var mapFunction = mapBookKeeping.mapFunction;
|
||||
var mapContext = mapBookKeeping.mapContext;
|
||||
var mappedChild = mapFunction.call(mapContext, child, name, i);
|
||||
// We found a component instance
|
||||
if (__DEV__) {
|
||||
throwIf(
|
||||
mapResult.hasOwnProperty(name),
|
||||
traverseAllChildren.DUPLICATE_KEY_ERROR
|
||||
);
|
||||
}
|
||||
// We found a component instance.
|
||||
invariant(
|
||||
!mapResult.hasOwnProperty(name),
|
||||
'mapAllChildren(...): Encountered two children with the same key, `%s`. ' +
|
||||
'Children keys must be unique.',
|
||||
name
|
||||
);
|
||||
mapResult[name] = mappedChild;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var keyMirror = require('keyMirror');
|
||||
var invariant = require('invariant');
|
||||
var mergeHelpers = require('mergeHelpers');
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
var ArrayStrategies = mergeHelpers.ArrayStrategies;
|
||||
var checkArrayStrategy = mergeHelpers.checkArrayStrategy;
|
||||
@@ -33,20 +32,6 @@ var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
|
||||
var isTerminal = mergeHelpers.isTerminal;
|
||||
var normalizeMergeArg = mergeHelpers.normalizeMergeArg;
|
||||
|
||||
var ERRORS = keyMirror({
|
||||
RUN_TIME_ARRAY_MERGE_FAIL: null
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
ERRORS = {
|
||||
RUN_TIME_ARRAY_MERGE_FAIL:
|
||||
"The caller has not supplied an ArrayStrategy. This is supported as " +
|
||||
"long as the data structures being merged do not contain two Arrays " +
|
||||
"that must be merged together, which is exactly what has just " +
|
||||
"happened. Change the call site to supply an Array merge resolver."
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Every deep merge function must handle merging in each of the following cases
|
||||
* at every level. We may refer to letters below in implementations. For each
|
||||
@@ -161,9 +146,10 @@ var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) {
|
||||
if (twoValIsTerminal) { // [E]
|
||||
one[key] = twoVal;
|
||||
} else if (twoValIsArray) { // [F]
|
||||
throwIf(
|
||||
!ArrayStrategies[arrayStrategy],
|
||||
ERRORS.RUN_TIME_ARRAY_MERGE_FAIL
|
||||
invariant(
|
||||
ArrayStrategies[arrayStrategy],
|
||||
'mergeDeepInto(...): Attempted to merge two arrays, but a valid ' +
|
||||
'ArrayStrategy was not specified.'
|
||||
);
|
||||
// Else: At this point, the only other valid option is `IndexByIndex`
|
||||
if (arrayStrategy === ArrayStrategies.Clobber) {
|
||||
@@ -234,7 +220,7 @@ var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) {
|
||||
*/
|
||||
var mergeDeepInto = function(one, twoParam, arrayStrategy) {
|
||||
var two = normalizeMergeArg(twoParam);
|
||||
checkArrayStrategy(arrayStrategy); // Will be checked twice, for now
|
||||
checkArrayStrategy(arrayStrategy); // Will be checked twice, for now.
|
||||
mergeDeepIntoObjects(one, two, arrayStrategy, 0);
|
||||
};
|
||||
|
||||
|
||||
+24
-49
@@ -20,8 +20,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
/**
|
||||
* Maximum number of levels to traverse. Will catch circular structures.
|
||||
@@ -29,41 +29,6 @@ var throwIf = require('throwIf');
|
||||
*/
|
||||
var MAX_MERGE_DEPTH = 36;
|
||||
|
||||
var ERRORS = keyMirror({
|
||||
MERGE_ARRAY_FAIL: null,
|
||||
MERGE_CORE_FAILURE: null,
|
||||
MERGE_TYPE_USAGE_FAILURE: null,
|
||||
MERGE_DEEP_MAX_LEVELS: null,
|
||||
MERGE_DEEP_NO_ARR_STRATEGY: null
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
ERRORS = {
|
||||
MERGE_ARRAY_FAIL:
|
||||
'Unsupported type passed to a merge function. You may have passed a ' +
|
||||
'structure that contains an array and the merge function does not know ' +
|
||||
'how to merge arrays. ',
|
||||
|
||||
MERGE_CORE_FAILURE:
|
||||
'Critical assumptions about the merge functions have been violated. ' +
|
||||
'This is the fault of the merge functions themselves, not necessarily ' +
|
||||
'the callers.',
|
||||
|
||||
MERGE_TYPE_USAGE_FAILURE:
|
||||
'Calling merge function with invalid types. You may call merge ' +
|
||||
'functions (non-array non-terminal) OR (null/undefined) arguments. ' +
|
||||
'mergeInto functions have the same requirements but with an added ' +
|
||||
'restriction that the first parameter must not be null/undefined.',
|
||||
|
||||
MERGE_DEEP_MAX_LEVELS:
|
||||
'Maximum deep merge depth exceeded. You may attempting to merge ' +
|
||||
'circular structures in an unsupported way.',
|
||||
MERGE_DEEP_NO_ARR_STRATEGY:
|
||||
'You must provide an array strategy to deep merge functions to ' +
|
||||
'instruct the deep merge how to resolve merging two arrays.'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* We won't worry about edge cases like new String('x') or new Boolean(true).
|
||||
* Functions are considered terminals, and arrays are not.
|
||||
@@ -99,9 +64,11 @@ var mergeHelpers = {
|
||||
* @param {*} two Array to merge from.
|
||||
*/
|
||||
checkMergeArrayArgs: function(one, two) {
|
||||
throwIf(
|
||||
!Array.isArray(one) || !Array.isArray(two),
|
||||
ERRORS.MERGE_CORE_FAILURE
|
||||
invariant(
|
||||
Array.isArray(one) && Array.isArray(two),
|
||||
'Critical assumptions about the merge functions have been violated. ' +
|
||||
'This is the fault of the merge functions themselves, not necessarily ' +
|
||||
'the callers.'
|
||||
);
|
||||
},
|
||||
|
||||
@@ -118,7 +85,12 @@ var mergeHelpers = {
|
||||
* @param {*} arg
|
||||
*/
|
||||
checkMergeObjectArg: function(arg) {
|
||||
throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
|
||||
invariant(
|
||||
!isTerminal(arg) && !Array.isArray(arg),
|
||||
'Critical assumptions about the merge functions have been violated. ' +
|
||||
'This is the fault of the merge functions themselves, not necessarily ' +
|
||||
'the callers.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -128,19 +100,23 @@ var mergeHelpers = {
|
||||
* @param {number} Level of recursion to validate against maximum.
|
||||
*/
|
||||
checkMergeLevel: function(level) {
|
||||
throwIf(level >= MAX_MERGE_DEPTH, ERRORS.MERGE_DEEP_MAX_LEVELS);
|
||||
invariant(
|
||||
level < MAX_MERGE_DEPTH,
|
||||
'Maximum deep merge depth exceeded. You may be attempting to merge ' +
|
||||
'circular structures in an unsupported way.'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks that a merge was not given a circular object or an object that had
|
||||
* too great of depth.
|
||||
* Checks that the supplied merge strategy is valid.
|
||||
*
|
||||
* @param {number} Level of recursion to validate against maximum.
|
||||
* @param {string} Array merge strategy.
|
||||
*/
|
||||
checkArrayStrategy: function(strategy) {
|
||||
throwIf(
|
||||
strategy !== undefined && !(strategy in mergeHelpers.ArrayStrategies),
|
||||
ERRORS.MERGE_DEEP_NO_ARR_STRATEGY
|
||||
invariant(
|
||||
strategy === undefined || strategy in mergeHelpers.ArrayStrategies,
|
||||
'You must provide an array strategy to deep merge functions to ' +
|
||||
'instruct the deep merge how to resolve merging two arrays.'
|
||||
);
|
||||
},
|
||||
|
||||
@@ -154,9 +130,8 @@ var mergeHelpers = {
|
||||
ArrayStrategies: keyMirror({
|
||||
Clobber: true,
|
||||
IndexByIndex: true
|
||||
}),
|
||||
})
|
||||
|
||||
ERRORS: ERRORS
|
||||
};
|
||||
|
||||
module.exports = mergeHelpers;
|
||||
|
||||
@@ -21,22 +21,7 @@
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactTextComponent = require('ReactTextComponent');
|
||||
|
||||
var throwIf = require('throwIf');
|
||||
|
||||
/**
|
||||
* @polyFill Array.isArray
|
||||
*/
|
||||
var DUPLICATE_KEY_ERROR = 'DUPLICATE_KEY_ERROR';
|
||||
var INVALID_CHILD = 'INVALID_CHILD';
|
||||
if (__DEV__) {
|
||||
INVALID_CHILD =
|
||||
'You may not pass a child of that type to a React component. It ' +
|
||||
'is a common mistake to try to pass a standard browser DOM element ' +
|
||||
'as a child of a React component.';
|
||||
DUPLICATE_KEY_ERROR =
|
||||
'You have two children with identical keys. Make sure that you set the ' +
|
||||
'"key" property to a unique value such as a row ID.';
|
||||
}
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* TODO: Test that:
|
||||
@@ -88,7 +73,11 @@ var traverseAllChildrenImpl =
|
||||
subtreeCount = 1;
|
||||
} else {
|
||||
if (type === 'object') {
|
||||
throwIf(children && children.nodeType === 1, INVALID_CHILD);
|
||||
invariant(
|
||||
children || children.nodeType !== 1,
|
||||
'traverseAllChildren(...): Encountered an invalid child; DOM ' +
|
||||
'elements are not valid children of React components.'
|
||||
);
|
||||
for (var key in children) {
|
||||
if (children.hasOwnProperty(key)) {
|
||||
subtreeCount += traverseAllChildrenImpl(
|
||||
@@ -135,10 +124,4 @@ function traverseAllChildren(children, callback, traverseContext) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the error code for use in other walking/mapping code.
|
||||
*/
|
||||
traverseAllChildren.DUPLICATE_KEY_ERROR = DUPLICATE_KEY_ERROR;
|
||||
|
||||
|
||||
module.exports = traverseAllChildren;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
jasmine = parent.jasmine;
|
||||
jasmine.exposeFrom(window);
|
||||
|
||||
console = parent.console;
|
||||
callPhantom = parent.callPhantom;
|
||||
</script>
|
||||
<script src="react-test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
require(window.frameElement.getAttribute("test"));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+14
-13
@@ -2,21 +2,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="jasmine.css" />
|
||||
<style type="text/css">
|
||||
iframe {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
left: -1000px;
|
||||
top: -1000px;
|
||||
}
|
||||
</style>
|
||||
<script src="jasmine.js"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
jasmine.getEnv().execute();
|
||||
};
|
||||
</script>
|
||||
<script src="react-test.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
ENABLE_TESTS_HERE
|
||||
|
||||
(function(env) {
|
||||
// Clean up any nodes the previous test might have added.
|
||||
env.afterEach(function() {
|
||||
harness.removeNextSiblings(document.body);
|
||||
harness.removeNextSiblings(document.getElementById("HTMLReporter"));
|
||||
});
|
||||
|
||||
env.execute();
|
||||
})(jasmine.getEnv());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-10
@@ -34,14 +34,13 @@ while (argv.length > 0) {
|
||||
rest.push(arg);
|
||||
}
|
||||
|
||||
// Dynamically interpolate the individual test <iframe>s.
|
||||
// Dynamically enable the individual tests.
|
||||
var indexHtml = fs.read("index.html").replace(
|
||||
/<body>([\s\S]*?)<\/body>/im,
|
||||
function(outer, inner) {
|
||||
return "<body>" + tests.map(function(test) {
|
||||
return '\n <iframe src="frame.html" test=' +
|
||||
JSON.stringify(test) + '></iframe>';
|
||||
}).join("") + inner + "</body>";
|
||||
/^(\s*)ENABLE_TESTS_HERE/m,
|
||||
function(placeholder, leadingSpace) {
|
||||
return leadingSpace + tests.map(function(testID) {
|
||||
return "harness.enableTest(" + JSON.stringify(testID) + ");";
|
||||
}).join("\n" + leadingSpace);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -63,9 +62,6 @@ server.listen(port, function(req, res) {
|
||||
file = "../build/" + file;
|
||||
break;
|
||||
|
||||
case "frame.html":
|
||||
break;
|
||||
|
||||
case "":
|
||||
default:
|
||||
file = "index.html";
|
||||
|
||||
Reference in New Issue
Block a user