+
+
+
{{ end }}
diff --git a/test/.jshintrc b/test/.jshintrc
new file mode 100644
index 0000000..baa5704
--- /dev/null
+++ b/test/.jshintrc
@@ -0,0 +1,40 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": false,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "false",
+ "regexp": true,
+ "undef": true,
+ "unused": false,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "white": true,
+ "newcap": false,
+ "globals": {
+ "after": false,
+ "afterEach": false,
+ "react": false,
+ "before": false,
+ "beforeEach": false,
+ "browser": false,
+ "describe": false,
+ "expect": false,
+ "inject": false,
+ "it": false,
+ "spyOn": false,
+ "jasmine": false,
+ "spyOnConstructor": false,
+ "React": true
+ }
+}
+
diff --git a/test/helpers/createComponent.js b/test/helpers/createComponent.js
new file mode 100644
index 0000000..8a0c1da
--- /dev/null
+++ b/test/helpers/createComponent.js
@@ -0,0 +1,27 @@
+/**
+ * Function to get the shallow output for a given component
+ * As we are using phantom.js, we also need to include the fn.proto.bind shim!
+ *
+ * @see http://simonsmith.io/unit-testing-react-components-without-a-dom/
+ * @author somonsmith
+ */
+
+// Add missing methods to phantom.js
+import './pack/phantomjs-shims';
+
+import React from 'react/addons';
+const TestUtils = React.addons.TestUtils;
+
+/**
+ * Get the shallow rendered component
+ *
+ * @param {Object} component The component to return the output for
+ * @param {Object} props [optional] The components properties
+ * @param {Mixed} ...children [optional] List of children
+ * @return {Object} Shallow rendered output
+ */
+export default function createComponent(component, props = {}, ...children) {
+ const shallowRenderer = TestUtils.createRenderer();
+ shallowRenderer.render(React.createElement(component, props, children.length > 1 ? children : children[0]));
+ return shallowRenderer.getRenderOutput();
+}
diff --git a/test/helpers/pack/phantomjs-shims.js b/test/helpers/pack/phantomjs-shims.js
new file mode 100644
index 0000000..7da307d
--- /dev/null
+++ b/test/helpers/pack/phantomjs-shims.js
@@ -0,0 +1,34 @@
+(function() {
+
+var Ap = Array.prototype;
+var slice = Ap.slice;
+var Fp = Function.prototype;
+
+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);
+
+ 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;
+ };
+}
+})();
diff --git a/test/helpers/react/addons.js b/test/helpers/react/addons.js
new file mode 100755
index 0000000..0bf44fe
--- /dev/null
+++ b/test/helpers/react/addons.js
@@ -0,0 +1,16336 @@
+/**
+ * React (with addons) v0.9.0-alpha
+ */
+!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.React=e():"undefined"!=typeof global?global.React=e():"undefined"!=typeof self&&(self.React=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o -1;
+}
+
+var CSSCore = {
+
+ /**
+ * Adds the class passed in to the element if it doesn't already have it.
+ *
+ * @param {DOMElement} element the element to set the class on
+ * @param {string} className the CSS className
+ * @return {DOMElement} the element passed in
+ */
+ addClass: function(element, className) {
+ ("production" !== "development" ? invariant(
+ !/\s/.test(className),
+ 'CSSCore.addClass takes only a single class name. "%s" contains ' +
+ 'multiple classes.', className
+ ) : invariant(!/\s/.test(className)));
+
+ if (className) {
+ if (element.classList) {
+ element.classList.add(className);
+ } else if (!hasClass(element, className)) {
+ element.className = element.className + ' ' + className;
+ }
+ }
+ return element;
+ },
+
+ /**
+ * Removes the class passed in from the element
+ *
+ * @param {DOMElement} element the element to set the class on
+ * @param {string} className the CSS className
+ * @return {DOMElement} the element passed in
+ */
+ removeClass: function(element, className) {
+ ("production" !== "development" ? invariant(
+ !/\s/.test(className),
+ 'CSSCore.removeClass takes only a single class name. "%s" contains ' +
+ 'multiple classes.', className
+ ) : invariant(!/\s/.test(className)));
+
+ if (className) {
+ if (element.classList) {
+ element.classList.remove(className);
+ } else if (hasClass(element, className)) {
+ element.className = element.className
+ .replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), '$1')
+ .replace(/\s+/g, ' ') // multiple spaces to one
+ .replace(/^\s*|\s*$/g, ''); // trim the ends
+ }
+ }
+ return element;
+ },
+
+ /**
+ * Helper to add or remove a class from an element based on a condition.
+ *
+ * @param {DOMElement} element the element to set the class on
+ * @param {string} className the CSS className
+ * @param {*} bool condition to whether to add or remove the class
+ * @return {DOMElement} the element passed in
+ */
+ conditionClass: function(element, className, bool) {
+ return (bool ? CSSCore.addClass : CSSCore.removeClass)(element, className);
+ }
+};
+
+module.exports = CSSCore;
+
+},{"./invariant":113}],3:[function(require,module,exports){
+/**
+ * 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 CSSProperty
+ */
+
+"use strict";
+
+/**
+ * CSS properties which accept numbers but are not in units of "px".
+ */
+var isUnitlessNumber = {
+ fillOpacity: true,
+ fontWeight: true,
+ lineHeight: true,
+ opacity: true,
+ orphans: true,
+ zIndex: true,
+ zoom: true
+};
+
+/**
+ * Most style properties can be unset by doing .style[prop] = '' but IE8
+ * doesn't like doing that with shorthand properties so for the properties that
+ * IE8 breaks on, which are listed here, we instead unset each of the
+ * individual properties. See http://bugs.jquery.com/ticket/12385.
+ * The 4-value 'clock' properties like margin, padding, border-width seem to
+ * behave without any problems. Curiously, list-style works too without any
+ * special prodding.
+ */
+var shorthandPropertyExpansions = {
+ background: {
+ backgroundImage: true,
+ backgroundPosition: true,
+ backgroundRepeat: true,
+ backgroundColor: true
+ },
+ border: {
+ borderWidth: true,
+ borderStyle: true,
+ borderColor: true
+ },
+ borderBottom: {
+ borderBottomWidth: true,
+ borderBottomStyle: true,
+ borderBottomColor: true
+ },
+ borderLeft: {
+ borderLeftWidth: true,
+ borderLeftStyle: true,
+ borderLeftColor: true
+ },
+ borderRight: {
+ borderRightWidth: true,
+ borderRightStyle: true,
+ borderRightColor: true
+ },
+ borderTop: {
+ borderTopWidth: true,
+ borderTopStyle: true,
+ borderTopColor: true
+ },
+ font: {
+ fontStyle: true,
+ fontVariant: true,
+ fontWeight: true,
+ fontSize: true,
+ lineHeight: true,
+ fontFamily: true
+ }
+};
+
+var CSSProperty = {
+ isUnitlessNumber: isUnitlessNumber,
+ shorthandPropertyExpansions: shorthandPropertyExpansions
+};
+
+module.exports = CSSProperty;
+
+},{}],4:[function(require,module,exports){
+/**
+ * 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 CSSPropertyOperations
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var CSSProperty = require("./CSSProperty");
+
+var dangerousStyleValue = require("./dangerousStyleValue");
+var escapeTextForBrowser = require("./escapeTextForBrowser");
+var hyphenate = require("./hyphenate");
+var memoizeStringOnly = require("./memoizeStringOnly");
+
+var processStyleName = memoizeStringOnly(function(styleName) {
+ return escapeTextForBrowser(hyphenate(styleName));
+});
+
+/**
+ * Operations for dealing with CSS properties.
+ */
+var CSSPropertyOperations = {
+
+ /**
+ * Serializes a mapping of style properties for use as inline styles:
+ *
+ * > createMarkupForStyles({width: '200px', height: 0})
+ * "width:200px;height:0;"
+ *
+ * Undefined values are ignored so that declarative programming is easier.
+ *
+ * @param {object} styles
+ * @return {?string}
+ */
+ createMarkupForStyles: function(styles) {
+ var serialized = '';
+ for (var styleName in styles) {
+ if (!styles.hasOwnProperty(styleName)) {
+ continue;
+ }
+ var styleValue = styles[styleName];
+ if (styleValue != null) {
+ serialized += processStyleName(styleName) + ':';
+ serialized += dangerousStyleValue(styleName, styleValue) + ';';
+ }
+ }
+ return serialized || null;
+ },
+
+ /**
+ * Sets the value for multiple styles on a node. If a value is specified as
+ * '' (empty string), the corresponding style property will be unset.
+ *
+ * @param {DOMElement} node
+ * @param {object} styles
+ */
+ setValueForStyles: function(node, styles) {
+ var style = node.style;
+ for (var styleName in styles) {
+ if (!styles.hasOwnProperty(styleName)) {
+ continue;
+ }
+ var styleValue = dangerousStyleValue(styleName, styles[styleName]);
+ if (styleValue) {
+ style[styleName] = styleValue;
+ } else {
+ var expansion = CSSProperty.shorthandPropertyExpansions[styleName];
+ if (expansion) {
+ // Shorthand property that IE8 won't like unsetting, so unset each
+ // component to placate it
+ for (var individualStyleName in expansion) {
+ style[individualStyleName] = '';
+ }
+ } else {
+ style[styleName] = '';
+ }
+ }
+ }
+ }
+
+};
+
+module.exports = CSSPropertyOperations;
+
+},{"./CSSProperty":3,"./dangerousStyleValue":97,"./escapeTextForBrowser":99,"./hyphenate":112,"./memoizeStringOnly":121}],5:[function(require,module,exports){
+/**
+ * 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 CallbackRegistry
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var listenerBank = {};
+
+/**
+ * Stores "listeners" by `registrationName`/`id`. There should be at most one
+ * "listener" per `registrationName`/`id` in the `listenerBank`.
+ *
+ * Access listeners via `listenerBank[registrationName][id]`.
+ *
+ * @class CallbackRegistry
+ * @internal
+ */
+var CallbackRegistry = {
+
+ /**
+ * Stores `listener` at `listenerBank[registrationName][id]`. Is idempotent.
+ *
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ * @param {?function} listener The callback to store.
+ */
+ putListener: function(id, registrationName, listener) {
+ var bankForRegistrationName =
+ listenerBank[registrationName] || (listenerBank[registrationName] = {});
+ bankForRegistrationName[id] = listener;
+ },
+
+ /**
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ * @return {?function} The stored callback.
+ */
+ getListener: function(id, registrationName) {
+ var bankForRegistrationName = listenerBank[registrationName];
+ return bankForRegistrationName && bankForRegistrationName[id];
+ },
+
+ /**
+ * Deletes a listener from the registration bank.
+ *
+ * @param {string} id ID of the DOM element.
+ * @param {string} registrationName Name of listener (e.g. `onClick`).
+ */
+ deleteListener: function(id, registrationName) {
+ var bankForRegistrationName = listenerBank[registrationName];
+ if (bankForRegistrationName) {
+ delete bankForRegistrationName[id];
+ }
+ },
+
+ /**
+ * Deletes all listeners for the DOM element with the supplied ID.
+ *
+ * @param {string} id ID of the DOM element.
+ */
+ deleteAllListeners: function(id) {
+ for (var registrationName in listenerBank) {
+ delete listenerBank[registrationName][id];
+ }
+ },
+
+ /**
+ * This is needed for tests only. Do not use!
+ */
+ __purge: function() {
+ listenerBank = {};
+ }
+
+};
+
+module.exports = CallbackRegistry;
+
+},{}],6:[function(require,module,exports){
+/**
+ * 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 ChangeEventPlugin
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventPluginHub = require("./EventPluginHub");
+var EventPropagators = require("./EventPropagators");
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var SyntheticEvent = require("./SyntheticEvent");
+
+var isEventSupported = require("./isEventSupported");
+var isTextInputElement = require("./isTextInputElement");
+var keyOf = require("./keyOf");
+
+var topLevelTypes = EventConstants.topLevelTypes;
+
+var eventTypes = {
+ change: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onChange: null}),
+ captured: keyOf({onChangeCapture: null})
+ }
+ }
+};
+
+/**
+ * For IE shims
+ */
+var activeElement = null;
+var activeElementID = null;
+var activeElementValue = null;
+var activeElementValueProp = null;
+
+/**
+ * SECTION: handle `change` event
+ */
+function shouldUseChangeEvent(elem) {
+ return (
+ elem.nodeName === 'SELECT' ||
+ (elem.nodeName === 'INPUT' && elem.type === 'file')
+ );
+}
+
+var doesChangeEventBubble = false;
+if (ExecutionEnvironment.canUseDOM) {
+ // See `handleChange` comment below
+ doesChangeEventBubble = isEventSupported('change') && (
+ !('documentMode' in document) || document.documentMode > 8
+ );
+}
+
+function manualDispatchChangeEvent(nativeEvent) {
+ var event = SyntheticEvent.getPooled(
+ eventTypes.change,
+ activeElementID,
+ nativeEvent
+ );
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+
+ // If change bubbled, we'd just bind to it like all the other events
+ // and have it go through ReactEventTopLevelCallback. Since it doesn't, we
+ // manually listen for the change event and so we have to enqueue and
+ // process the abstract event manually.
+ EventPluginHub.enqueueEvents(event);
+ EventPluginHub.processEventQueue();
+}
+
+function startWatchingForChangeEventIE8(target, targetID) {
+ activeElement = target;
+ activeElementID = targetID;
+ activeElement.attachEvent('onchange', manualDispatchChangeEvent);
+}
+
+function stopWatchingForChangeEventIE8() {
+ if (!activeElement) {
+ return;
+ }
+ activeElement.detachEvent('onchange', manualDispatchChangeEvent);
+ activeElement = null;
+ activeElementID = null;
+}
+
+function getTargetIDForChangeEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topChange) {
+ return topLevelTargetID;
+ }
+}
+function handleEventsForChangeEventIE8(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topFocus) {
+ // stopWatching() should be a noop here but we call it just in case we
+ // missed a blur event somehow.
+ stopWatchingForChangeEventIE8();
+ startWatchingForChangeEventIE8(topLevelTarget, topLevelTargetID);
+ } else if (topLevelType === topLevelTypes.topBlur) {
+ stopWatchingForChangeEventIE8();
+ }
+}
+
+
+/**
+ * SECTION: handle `input` event
+ */
+var isInputEventSupported = false;
+if (ExecutionEnvironment.canUseDOM) {
+ // IE9 claims to support the input event but fails to trigger it when
+ // deleting text, so we ignore its input events
+ isInputEventSupported = isEventSupported('input') && (
+ !('documentMode' in document) || document.documentMode > 9
+ );
+}
+
+/**
+ * (For old IE.) Replacement getter/setter for the `value` property that gets
+ * set on the active element.
+ */
+var newValueProp = {
+ get: function() {
+ return activeElementValueProp.get.call(this);
+ },
+ set: function(val) {
+ // Cast to a string so we can do equality checks.
+ activeElementValue = '' + val;
+ activeElementValueProp.set.call(this, val);
+ }
+};
+
+/**
+ * (For old IE.) Starts tracking propertychange events on the passed-in element
+ * and override the value property so that we can distinguish user events from
+ * value changes in JS.
+ */
+function startWatchingForValueChange(target, targetID) {
+ activeElement = target;
+ activeElementID = targetID;
+ activeElementValue = target.value;
+ activeElementValueProp = Object.getOwnPropertyDescriptor(
+ target.constructor.prototype,
+ 'value'
+ );
+
+ Object.defineProperty(activeElement, 'value', newValueProp);
+ activeElement.attachEvent('onpropertychange', handlePropertyChange);
+}
+
+/**
+ * (For old IE.) Removes the event listeners from the currently-tracked element,
+ * if any exists.
+ */
+function stopWatchingForValueChange() {
+ if (!activeElement) {
+ return;
+ }
+
+ // delete restores the original property definition
+ delete activeElement.value;
+ activeElement.detachEvent('onpropertychange', handlePropertyChange);
+
+ activeElement = null;
+ activeElementID = null;
+ activeElementValue = null;
+ activeElementValueProp = null;
+}
+
+/**
+ * (For old IE.) Handles a propertychange event, sending a `change` event if
+ * the value of the active element has changed.
+ */
+function handlePropertyChange(nativeEvent) {
+ if (nativeEvent.propertyName !== 'value') {
+ return;
+ }
+ var value = nativeEvent.srcElement.value;
+ if (value === activeElementValue) {
+ return;
+ }
+ activeElementValue = value;
+
+ manualDispatchChangeEvent(nativeEvent);
+}
+
+/**
+ * If a `change` event should be fired, returns the target's ID.
+ */
+function getTargetIDForInputEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topInput) {
+ // In modern browsers (i.e., not IE8 or IE9), the input event is exactly
+ // what we want so fall through here and trigger an abstract event
+ return topLevelTargetID;
+ }
+}
+
+// For IE8 and IE9.
+function handleEventsForInputEventIE(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topFocus) {
+ // In IE8, we can capture almost all .value changes by adding a
+ // propertychange handler and looking for events with propertyName
+ // equal to 'value'
+ // In IE9, propertychange fires for most input events but is buggy and
+ // doesn't fire when text is deleted, but conveniently, selectionchange
+ // appears to fire in all of the remaining cases so we catch those and
+ // forward the event if the value has changed
+ // In either case, we don't want to call the event handler if the value
+ // is changed from JS so we redefine a setter for `.value` that updates
+ // our activeElementValue variable, allowing us to ignore those changes
+ //
+ // stopWatching() should be a noop here but we call it just in case we
+ // missed a blur event somehow.
+ stopWatchingForValueChange();
+ startWatchingForValueChange(topLevelTarget, topLevelTargetID);
+ } else if (topLevelType === topLevelTypes.topBlur) {
+ stopWatchingForValueChange();
+ }
+}
+
+// For IE8 and IE9.
+function getTargetIDForInputEventIE(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topSelectionChange ||
+ topLevelType === topLevelTypes.topKeyUp ||
+ topLevelType === topLevelTypes.topKeyDown) {
+ // On the selectionchange event, the target is just document which isn't
+ // helpful for us so just check activeElement instead.
+ //
+ // 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire
+ // propertychange on the first input event after setting `value` from a
+ // script and fires only keydown, keypress, keyup. Catching keyup usually
+ // gets it and catching keydown lets us fire an event for the first
+ // keystroke if user does a key repeat (it'll be a little delayed: right
+ // before the second keystroke). Other input methods (e.g., paste) seem to
+ // fire selectionchange normally.
+ if (activeElement && activeElement.value !== activeElementValue) {
+ activeElementValue = activeElement.value;
+ return activeElementID;
+ }
+ }
+}
+
+
+/**
+ * SECTION: handle `click` event
+ */
+function shouldUseClickEvent(elem) {
+ // Use the `click` event to detect changes to checkbox and radio inputs.
+ // This approach works across all browsers, whereas `change` does not fire
+ // until `blur` in IE8.
+ return (
+ elem.nodeName === 'INPUT' &&
+ (elem.type === 'checkbox' || elem.type === 'radio')
+ );
+}
+
+function getTargetIDForClickEvent(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID) {
+ if (topLevelType === topLevelTypes.topClick) {
+ return topLevelTargetID;
+ }
+}
+
+/**
+ * This plugin creates an `onChange` event that normalizes change events
+ * across form elements. This event fires at a time when it's possible to
+ * change the element's value without seeing a flicker.
+ *
+ * Supported elements are:
+ * - input (see `isTextInputElement`)
+ * - textarea
+ * - select
+ */
+var ChangeEventPlugin = {
+
+ eventTypes: eventTypes,
+
+ /**
+ * @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) {
+
+ var getTargetIDFunc, handleEventFunc;
+ if (shouldUseChangeEvent(topLevelTarget)) {
+ if (doesChangeEventBubble) {
+ getTargetIDFunc = getTargetIDForChangeEvent;
+ } else {
+ handleEventFunc = handleEventsForChangeEventIE8;
+ }
+ } else if (isTextInputElement(topLevelTarget)) {
+ if (isInputEventSupported) {
+ getTargetIDFunc = getTargetIDForInputEvent;
+ } else {
+ getTargetIDFunc = getTargetIDForInputEventIE;
+ handleEventFunc = handleEventsForInputEventIE;
+ }
+ } else if (shouldUseClickEvent(topLevelTarget)) {
+ getTargetIDFunc = getTargetIDForClickEvent;
+ }
+
+ if (getTargetIDFunc) {
+ var targetID = getTargetIDFunc(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID
+ );
+ if (targetID) {
+ var event = SyntheticEvent.getPooled(
+ eventTypes.change,
+ targetID,
+ nativeEvent
+ );
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+ }
+ }
+
+ if (handleEventFunc) {
+ handleEventFunc(
+ topLevelType,
+ topLevelTarget,
+ topLevelTargetID
+ );
+ }
+ }
+
+};
+
+module.exports = ChangeEventPlugin;
+
+},{"./EventConstants":15,"./EventPluginHub":17,"./EventPropagators":20,"./ExecutionEnvironment":21,"./SyntheticEvent":80,"./isEventSupported":114,"./isTextInputElement":116,"./keyOf":120}],7:[function(require,module,exports){
+/**
+ * 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 CompositionEventPlugin
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var EventConstants = require("./EventConstants");
+var EventPropagators = require("./EventPropagators");
+var ExecutionEnvironment = require("./ExecutionEnvironment");
+var ReactInputSelection = require("./ReactInputSelection");
+var SyntheticCompositionEvent = require("./SyntheticCompositionEvent");
+
+var getTextContentAccessor = require("./getTextContentAccessor");
+var keyOf = require("./keyOf");
+
+var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
+var START_KEYCODE = 229;
+
+var useCompositionEvent = ExecutionEnvironment.canUseDOM &&
+ 'CompositionEvent' in window;
+var topLevelTypes = EventConstants.topLevelTypes;
+var currentComposition = null;
+
+// Events and their corresponding property names.
+var eventTypes = {
+ compositionEnd: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionEnd: null}),
+ captured: keyOf({onCompositionEndCapture: null})
+ }
+ },
+ compositionStart: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionStart: null}),
+ captured: keyOf({onCompositionStartCapture: null})
+ }
+ },
+ compositionUpdate: {
+ phasedRegistrationNames: {
+ bubbled: keyOf({onCompositionUpdate: null}),
+ captured: keyOf({onCompositionUpdateCapture: null})
+ }
+ }
+};
+
+/**
+ * Translate native top level events into event types.
+ *
+ * @param {string} topLevelType
+ * @return {object}
+ */
+function getCompositionEventType(topLevelType) {
+ switch (topLevelType) {
+ case topLevelTypes.topCompositionStart:
+ return eventTypes.compositionStart;
+ case topLevelTypes.topCompositionEnd:
+ return eventTypes.compositionEnd;
+ case topLevelTypes.topCompositionUpdate:
+ return eventTypes.compositionUpdate;
+ }
+}
+
+/**
+ * Does our fallback best-guess model think this event signifies that
+ * composition has begun?
+ *
+ * @param {string} topLevelType
+ * @param {object} nativeEvent
+ * @return {boolean}
+ */
+function isFallbackStart(topLevelType, nativeEvent) {
+ return (
+ topLevelType === topLevelTypes.topKeyDown &&
+ nativeEvent.keyCode === START_KEYCODE
+ );
+}
+
+/**
+ * Does our fallback mode think that this event is the end of composition?
+ *
+ * @param {string} topLevelType
+ * @param {object} nativeEvent
+ * @return {boolean}
+ */
+function isFallbackEnd(topLevelType, nativeEvent) {
+ switch (topLevelType) {
+ case topLevelTypes.topKeyUp:
+ // Command keys insert or clear IME input.
+ return (END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1);
+ case topLevelTypes.topKeyDown:
+ // Expect IME keyCode on each keydown. If we get any other
+ // code we must have exited earlier.
+ return (nativeEvent.keyCode !== START_KEYCODE);
+ case topLevelTypes.topKeyPress:
+ case topLevelTypes.topMouseDown:
+ case topLevelTypes.topBlur:
+ // Events are not possible without cancelling IME.
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * Helper class stores information about selection and document state
+ * so we can figure out what changed at a later date.
+ *
+ * @param {DOMEventTarget} root
+ */
+function FallbackCompositionState(root) {
+ this.root = root;
+ this.startSelection = ReactInputSelection.getSelection(root);
+ this.startValue = this.getText();
+}
+
+/**
+ * Get current text of input.
+ *
+ * @return {string}
+ */
+FallbackCompositionState.prototype.getText = function() {
+ return this.root.value || this.root[getTextContentAccessor()];
+};
+
+/**
+ * Text that has changed since the start of composition.
+ *
+ * @return {string}
+ */
+FallbackCompositionState.prototype.getData = function() {
+ var endValue = this.getText();
+ var prefixLength = this.startSelection.start;
+ var suffixLength = this.startValue.length - this.startSelection.end;
+
+ return endValue.substr(
+ prefixLength,
+ endValue.length - suffixLength - prefixLength
+ );
+};
+
+/**
+ * This plugin creates `onCompositionStart`, `onCompositionUpdate` and
+ * `onCompositionEnd` events on inputs, textareas and contentEditable
+ * nodes.
+ */
+var CompositionEventPlugin = {
+
+ eventTypes: eventTypes,
+
+ /**
+ * @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) {
+
+ var eventType;
+ var data;
+
+ if (useCompositionEvent) {
+ eventType = getCompositionEventType(topLevelType);
+ } else if (!currentComposition) {
+ if (isFallbackStart(topLevelType, nativeEvent)) {
+ eventType = eventTypes.start;
+ currentComposition = new FallbackCompositionState(topLevelTarget);
+ }
+ } else if (isFallbackEnd(topLevelType, nativeEvent)) {
+ eventType = eventTypes.compositionEnd;
+ data = currentComposition.getData();
+ currentComposition = null;
+ }
+
+ if (eventType) {
+ var event = SyntheticCompositionEvent.getPooled(
+ eventType,
+ topLevelTargetID,
+ nativeEvent
+ );
+ if (data) {
+ // Inject data generated from fallback path into the synthetic event.
+ // This matches the property of native CompositionEventInterface.
+ event.data = data;
+ }
+ EventPropagators.accumulateTwoPhaseDispatches(event);
+ return event;
+ }
+ }
+};
+
+module.exports = CompositionEventPlugin;
+
+},{"./EventConstants":15,"./EventPropagators":20,"./ExecutionEnvironment":21,"./ReactInputSelection":51,"./SyntheticCompositionEvent":79,"./getTextContentAccessor":110,"./keyOf":120}],8:[function(require,module,exports){
+/**
+ * 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 DOMChildrenOperations
+ * @typechecks static-only
+ */
+
+"use strict";
+
+var Danger = require("./Danger");
+var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes");
+
+var getTextContentAccessor = require("./getTextContentAccessor");
+
+/**
+ * The DOM property to use when setting text content.
+ *
+ * @type {string}
+ * @private
+ */
+var textContentAccessor = getTextContentAccessor() || 'NA';
+
+/**
+ * Inserts `childNode` as a child of `parentNode` at the `index`.
+ *
+ * @param {DOMElement} parentNode Parent node in which to insert.
+ * @param {DOMElement} childNode Child node to insert.
+ * @param {number} index Index at which to insert the child.
+ * @internal
+ */
+function insertChildAt(parentNode, childNode, index) {
+ var childNodes = parentNode.childNodes;
+ if (childNodes[index] === childNode) {
+ return;
+ }
+ // If `childNode` is already a child of `parentNode`, remove it so that
+ // computing `childNodes[index]` takes into account the removal.
+ if (childNode.parentNode === parentNode) {
+ parentNode.removeChild(childNode);
+ }
+ if (index >= childNodes.length) {
+ parentNode.appendChild(childNode);
+ } else {
+ parentNode.insertBefore(childNode, childNodes[index]);
+ }
+}
+
+/**
+ * Operations for updating with DOM children.
+ */
+var DOMChildrenOperations = {
+
+ dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
+
+ /**
+ * Updates a component's children by processing a series of updates. The
+ * update configurations are each expected to have a `parentNode` property.
+ *
+ * @param {array